Sfoglia il codice sorgente

应用端通过http向调试工具发送信息

mrh 3 anni fa
parent
commit
e8892bbf3b
9 ha cambiato i file con 429 aggiunte e 2 eliminazioni
  1. 6 0
      main.py
  2. 14 0
      mywifi.py
  3. 2 1
      python38/OneNet_communicate.py
  4. 41 0
      python38/OneNet_token.py
  5. 58 0
      python38/Onet_API_https.py
  6. 71 1
      python38/readme.md
  7. 43 0
      readme.md
  8. 4 0
      test.py
  9. 190 0
      umqtt/simple.py

+ 6 - 0
main.py

@@ -0,0 +1,6 @@
+import mywifi
+import esp_mqtt
+
+if __name__ == '__main__':
+    mywifi.do_connect()
+    esp_mqtt.my_mqtt()

+ 14 - 0
mywifi.py

@@ -0,0 +1,14 @@
+import network
+SSID = "Xiaomi_eng"
+PASSWORD = "88888888"
+wlan = network.WLAN(network.STA_IF)
+
+
+def do_connect():
+    wlan.active(True)
+    if not wlan.isconnected():
+        print('connecting to network...')
+        wlan.connect(SSID, PASSWORD)
+        while not wlan.isconnected():
+            pass
+    print('network config:', wlan.ifconfig())

+ 2 - 1
python38/OneNet_communicate.py

@@ -21,7 +21,7 @@ def on_connect(client, userdata, flags, rc):
     print("连接结果:" + mqtt.connack_string(rc))
 # 从服务器接收发布消息时的回调。
 def on_message(client, userdata, msg):
-    print(str(msg.payload,'utf-8'))
+    print("on_message:" ,str(msg.payload,'utf-8'))
 #当消息已经被发送给中间人,on_publish()回调将会被触发
 def on_publish(client, userdata, mid):
     print(str(mid))
@@ -65,6 +65,7 @@ def main():
     topic_cmd = '$sys/%s/%s/cmd/#' % (username, DEV_NAME)           # 设备接受命令主题
     topic_cmds = '$sys/%s/%s/cmd/request/' % (username, DEV_NAME)   # 设备接受命令主题
     topic_publish = '$sys/%s/%s/dp/post/json' %(username,DEV_NAME)
+    "$sys/{}/{}/dp/post/json/accepted".format(username,DEV_NAME)
     client.loop_start()
     count = 0
     while True:

+ 41 - 0
python38/OneNet_token.py

@@ -0,0 +1,41 @@
+import base64
+import hmac
+import time
+from urllib.parse import quote
+# base64 无法导入microPython,因为文件过大(5M),esp8266只有8Mflash空间
+# 因此要计算好之后再加入到嵌入式设备
+
+def cal_token(id, access_key):  # 官方文档给出的核心秘钥计算算法
+
+    version = '2018-10-31'
+
+    res = 'products/%s' % id  # 通过产品ID访问产品API
+
+    # 用户自定义token过期时间:一年后过期
+    et = str(int(time.time()) + 31536000)
+
+    # 签名方法,支持md5、sha1、sha256
+    method = 'sha1'
+
+    # 对access_key进行decode
+    key = base64.b64decode(access_key)
+
+    # 计算sign
+    org = et + '\n' + method + '\n' + res + '\n' + version
+    sign_b = hmac.new(key=key, msg=org.encode(), digestmod=method)
+    sign = base64.b64encode(sign_b.digest()).decode()
+
+    # value 部分进行url编码,method/res/version值较为简单无需编码
+    sign = quote(sign, safe='')
+    res = quote(res, safe='')
+
+    # token参数拼接
+    token = 'version=%s&res=%s&et=%s&method=%s&sign=%s' % (
+        version, res, et, method, sign)
+    return token
+
+if __name__ == '__main__':
+    PRO_ID = "522417" #产品ID
+    accesskey = "dgEP7iOL2/1kVNc1ykDvIhXSVqhb0tLnKinjU2RYpDs="  # 设备秘钥
+    password = cal_token(PRO_ID, accesskey)
+    print(password)

+ 58 - 0
python38/Onet_API_https.py

@@ -0,0 +1,58 @@
+import requests
+import json
+
+# 因为设备名是唯一的,加上 token 中有产品ID,因此很容易仅通过设备名就可以查询设备信息
+DEVICE_NAME = "emq_online"
+TOKEN = "version=2018-10-31&res=products%2F522417&et=1685551369&method=sha1&sign=9l9gkFQx7A%2B7B3fu8uU%2F089azps%3D"
+
+# 以产品为对象构建类
+class Api_OneNet():
+    def __init__(self, api_addr="https://api.heclouds.com", token=TOKEN) -> None:
+        self.api_addr = api_addr
+        self.token = token
+    
+    def get_device_info(self, device_name):
+        url = self.api_addr + f"/mqtt/v1/devices/{device_name}"
+        headers = {
+            'Authorization': self.token,
+            }
+        response = requests.request("GET", url, headers=headers)
+        print(type(response))
+        device_info = json.dumps(response.json(), indent=2, ensure_ascii=False)
+        print(device_info)
+        return device_info
+
+    def register_device(self, device_name):
+        url = self.api_addr + f'/mqtt/v1/devices/reg'
+        # TODO
+    
+    def device_cmd(self, device_id):
+        url = self.api_addr + f'/v1/synccmds?device_id={device_id}&timeout=30'
+        headers = {
+            'Authorization': self.token,
+        }
+        response = requests.request("POST", url, headers=headers, data="open")
+        res = json.dumps(response.json(), indent=2, ensure_ascii=False)
+        print(res)
+        return res
+
+    
+if __name__ == '__main__':
+    api = Api_OneNet()
+    api.device_cmd('952659214')
+    
+'''
+<class 'requests.models.Response'>
+{
+  "request_id": "7477d03e-815f-4973-9426-6e88d6699522",
+  "code": "onenet_common_success",
+  "code_no": "000000",
+  "message": null,
+  "data": {
+    "device_id": "952659214",
+    "name": "emq_online",
+    "pid": 522417,
+    "key": "bXgU8U0W1DAlyWcD22xrQNK/DXkwr6kFD7bLKQ2390w="
+  }
+}
+'''

+ 71 - 1
python38/readme.md

@@ -16,8 +16,78 @@ https://open.iot.10086.cn/doc/mqtt/book/manual/auth/token.html
 ## 网友参考
 https://blog.csdn.net/weixin_42410959/article/details/105897629?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165375763916782388012981%2522%252C%2522scm%2522%253A%252220140713.130102334..%2522%257D&request_id=165375763916782388012981&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~sobaiduend~default-1-105897629-null-null.142^v11^pc_search_result_control_group,157^v12^control&utm_term=python+mqtt+onenet&spm=1018.2226.3001.4187
 
-# mqtt通信
+# mqtt与代理通信
 由于上述只能连接,无法通信,参考其他文档:
 https://blog.csdn.net/ZHJ123CSDN/article/details/115137258?ops_request_misc=%257B%2522request%255Fid%2522%253A%2522165376451316782246419436%2522%252C%2522scm%2522%253A%252220140713.130102334.pc%255Fall.%2522%257D&request_id=165376451316782246419436&biz_id=0&utm_medium=distribute.pc_search_result.none-task-blog-2~all~first_rank_ecpm_v1~rank_v31_ecpm-10-115137258-null-null.142^v11^pc_search_result_control_group,157^v12^control&utm_term=OneNet+mqtt&spm=1018.2226.3001.4187
 
 在 OneNet_communicate-RaspberryPi.py 中有代码示例,但这是关于树莓派的,还需要移植一下,详见:OneNet_communicate.py
+
+## publish 消息
+
+这是成功发布"订阅设备数据点上报结果"的消息:
+```
+topic_publish:  $sys/522417/esp8266/dp/post/json
+topic_publish:  $sys/522417/esp8266/dp/post/json
+payload:  b"{'id': 160, 'dp': {'count': [{'v': 114}], 'random': [{'v': 224}], 'talk': [{'v': 'fuck you bitch!166'}]}}"
+```
+一般来说,只要用户名、密码正确,发布的topic协议如上,均能正常送达
+topic格式规范参考:https://open.iot.10086.cn/doc/v5/develop/detail/251
+```
+{
+    "id": 123,        
+    "dp": {             
+        "temperatrue": [{     
+            "v": 30,       
+            "t": 1552289676
+        }],
+        "power": [{     
+            "v": 4.5,        
+            "t": 1552289676 
+        }],
+        "status": [{
+                "v": {
+                    "color": "blue"
+                },
+                "t": 1552289677
+            },
+            {
+                "v": {
+                    "color": "red"
+                },
+                "t": 1552289678
+            }
+        ]
+    }
+}
+```
+**总结:格式仅支持json,必须要有id、dp、v参数,t可选,方括号也是必须的**
+
+## 订阅消息
+设备与服务端要双向通信,必须要订阅相关topic,**如果不订阅,设备将只能发消息而收不到任何内容**。
+see:https://open.iot.10086.cn/doc/v5/develop/detail/247
+例如订阅一个accepted的topic:$sys/{pid}/{device-name}/dp/post/json/accepted,此后任何发送给服务端的消息,都会收到一个应答,没有订阅则收不到应答消息。
+**注意 :在 `{pid}/{device-name}`中,哪台设备要订阅topic,就填写哪台设备的id和device-name,不能填写别人的**,
+
+# API命令(代理服务器)和设备
+如果进行设备管理,数据查询,设备命令交互等操作,需要用到平台提供的API
+see:https://open.iot.10086.cn/doc/v5/develop/detail/255
+
+因为设备无法通过mqtt方式直接告诉代理要转发给谁,只能通过https协议调用代理的API,由服务器执行消息转发、应答设备、下发消息等等
+## 查询设备
+在文件 Onet_API_https.py -> get_device_info 中,通过发送API查询指令得到设备信息
+
+## 注册设备
+TODO
+
+## 设备命令
+假设有esp8266开发板、服务端、电脑端,如果开发板要远程发消息给电脑,步骤如下:
+1. 开发板想要接受服务端的消息,需要先订阅命令相关的topic
+2. 建议开发板订阅来自服务端的所有命令的topic,如:$sys/{pid}/{device-name}/cmd/#,这样就能收到来自服务端所有的命令消息了。[参考文档](https://open.iot.10086.cn/doc/v5/develop/detail/247)
+3. 电脑端告诉服务端发送命令给开发板,需调用服务端API,以https协议POST方式发送数据内容[参考文档](https://open.iot.10086.cn/doc/v5/develop/detail/261)。
+4. 电脑端发送的https数据包中,header存放数据鉴权,body为用户自定义数据内容“例如:"open" ”。[参考](https://open.iot.10086.cn/doc/v5/develop/detail/255)
+5. 服务端收到 https 消息后,会将 body 内容以mqtt协议发送给开发板
+6. 开发板收到"$sys/{pid}/{device-name}/request/{cmdid} open",必须向服务端发送一个应答,应答格式为:$sys/{pid}/{device-name}/cmd/response/{cmdid} ,[参考](https://open.iot.10086.cn/doc/v5/develop/detail/252)。
+7. 服务端会把开发板应答成功的消息发送给电脑端。如果开发板不应答,电脑端会受到服务端一个time_out错误返回。
+
+## 设备镜像
+服务端记录设备状态的信息,例如 device id、device name 、创建时间、网络状态等等,用户也可以增加一些自定义属性如:版本号、地理位置、剩余电量、存储空间、上次更新时间、传感器状态、环境温度等等。总的来说设备镜像就是设备的信息或工作状态。

+ 43 - 0
readme.md

@@ -0,0 +1,43 @@
+
+# 官方固件无法使用
+https://micropython.org/download/esp8266/
+在乐鑫下载flash工具,选择bin文件烧录,不论是什么版本的bin文件、flash大小、还是SPI通信方式、波特率,
+程序运行后全部都是乱码
+
+幸好在b站视频看到教程:https://www.bilibili.com/video/BV1xh411o7Qp?p=1
+使用 uPyCraft.exe 工具可以烧录:
+MicroPython v1.9-6-g821dc27e-dirty on 2017-07-21; ESP module 
+
+或者下载官方推荐的工具 esptool.py 来烧录
+参考:E:\Engineer\Engineer_sync\one-chip-computer\micropython\micropython\ports\esp8266\README.md
+
+# 开始使用 MicroPython port to ESP8266
+参考中文文档:http://www.gdradio.com.cn/en/latet/esp8266/quickref.html#networking
+## 连接到本地WiFi网络:
+```
+def do_connect():
+    import network
+    wlan = network.WLAN(network.STA_IF)
+    wlan.active(True)
+    if not wlan.isconnected():
+        print('connecting to network...')
+        wlan.connect('Xiaomi_eng', '88888888')
+        while not wlan.isconnected():
+            pass
+    print('network config:', wlan.ifconfig())
+```
+在REPL中,使用 ctrl+E 进入粘贴模式,粘贴以上代码,再按Ctrl+D退出粘贴模式并运行代码,最后运行函数:do_connect()
+返回如下,说明联网成功:
+```
+#5 ets_task(4020f560, 28, 3fff9ef0, 10)
+connecting to network...
+network config: ('192.168.31.237', '255.255.255.0', '192.168.31.1', '192.168.31.1')
+```
+## 下载第三方扩展包
+有需要第三方库的,可以下载所需的包到esp8266
+参考:http://www.gdradio.com.cn/en/latet/reference/packages.html
+例如,我想下载pystone_lowmem模块安装到esp8266中,在REPL中输入以下代码:
+```
+import upip
+upip.install("micropython-pystone_lowmem")
+```

+ 4 - 0
test.py

@@ -0,0 +1,4 @@
+import mywifi
+
+print('network config:', mywifi.wlan.ifconfig())
+

+ 190 - 0
umqtt/simple.py

@@ -0,0 +1,190 @@
+import usocket as socket
+import ustruct as struct
+#from ubinascii import hexlify
+
+class MQTTException(Exception):
+    pass
+
+class MQTTClient:
+
+  def __init__(self, client_id, server, port=0, user=None, password=None, keepalive=0,ssl=False, ssl_params={}):
+    if port == 0:
+      port = 8883 if ssl else 1883
+    self.client_id = client_id
+    self.sock = None
+    self.addr = socket.getaddrinfo(server, port)[0][-1]
+    self.ssl = ssl
+    self.ssl_params = ssl_params
+    self.pid = 0
+    self.cb = None
+    self.user = user
+    self.pswd = password
+    self.keepalive = keepalive
+    self.lw_topic = None
+    self.lw_msg = None
+    self.lw_qos = 0
+    self.lw_retain = False
+
+  def _send_str(self, s):
+    self.sock.write(struct.pack("!H", len(s)))
+    self.sock.write(s)
+
+  def _recv_len(self):
+    n = 0
+    sh = 0
+    while 1:
+      b = self.sock.read(1)[0]
+      n |= (b & 0x7f) << sh
+      if not b & 0x80:
+        return n
+      sh += 7
+
+  def set_callback(self, f):
+    self.cb = f
+
+  def set_last_will(self, topic, msg, retain=False, qos=0):
+    assert 0 <= qos <= 2
+    assert topic
+    self.lw_topic = topic
+    self.lw_msg = msg
+    self.lw_qos = qos
+    self.lw_retain = retain
+
+  def connect(self, clean_session=True):
+    self.sock = socket.socket()
+    self.sock.connect(self.addr)
+    if self.ssl:
+      import ussl
+      self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
+    msg = bytearray(b"\x10\0\0\x04MQTT\x04\x02\0\0")
+    msg[1] = 10 + 2 + len(self.client_id)
+    msg[9] = clean_session << 1
+    if self.user is not None:
+      msg[1] += 2 + len(self.user) + 2 + len(self.pswd)
+      msg[9] |= 0xC0
+    if self.keepalive:
+      assert self.keepalive < 65536
+      msg[10] |= self.keepalive >> 8
+      msg[11] |= self.keepalive & 0x00FF
+    if self.lw_topic:
+      msg[1] += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
+      msg[9] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
+      msg[9] |= self.lw_retain << 5
+    self.sock.write(msg)
+    #print(hex(len(msg)), hexlify(msg, ":"))
+    self._send_str(self.client_id)
+    if self.lw_topic:
+      self._send_str(self.lw_topic)
+      self._send_str(self.lw_msg)
+    if self.user is not None:
+      self._send_str(self.user)
+      self._send_str(self.pswd)
+    resp = self.sock.read(4)
+    assert resp[0] == 0x20 and resp[1] == 0x02
+    if resp[3] != 0:
+      raise MQTTException(resp[3])
+    return resp[2] & 1
+
+  def disconnect(self):
+    self.sock.write(b"\xe0\0")
+    self.sock.close()
+
+  def ping(self):
+    self.sock.write(b"\xc0\0")
+
+  def publish(self, topic, msg, retain=False, qos=0):
+    pkt = bytearray(b"\x30\0\0\0")
+    pkt[0] |= qos << 1 | retain
+    sz = 2 + len(topic) + len(msg)
+    if qos > 0:
+      sz += 2
+    assert sz < 2097152
+    i = 1
+    while sz > 0x7f:
+      pkt[i] = (sz & 0x7f) | 0x80
+      sz >>= 7
+      i += 1
+    pkt[i] = sz
+    #print(hex(len(pkt)), hexlify(pkt, ":"))
+    self.sock.write(pkt, i + 1)
+    self._send_str(topic)
+    if qos > 0:
+      self.pid += 1
+      pid = self.pid
+      struct.pack_into("!H", pkt, 0, pid)
+      self.sock.write(pkt, 2)
+    self.sock.write(msg)
+    if qos == 1:
+      while 1:
+        op = self.wait_msg()
+        if op == 0x40:
+          sz = self.sock.read(1)
+          assert sz == b"\x02"
+          rcv_pid = self.sock.read(2)
+          rcv_pid = rcv_pid[0] << 8 | rcv_pid[1]
+          if pid == rcv_pid:
+            return
+    elif qos == 2:
+      assert 0
+
+  def subscribe(self, topic, qos=0):
+    assert self.cb is not None, "Subscribe callback is not set"
+    pkt = bytearray(b"\x82\0\0\0")
+    self.pid += 1
+    struct.pack_into("!BH", pkt, 1, 2 + 2 + len(topic) + 1, self.pid)
+    #print(hex(len(pkt)), hexlify(pkt, ":"))
+    self.sock.write(pkt)
+    self._send_str(topic)
+    self.sock.write(qos.to_bytes(1, "little"))
+    while 1:
+      op = self.wait_msg()
+      if op == 0x90:
+        resp = self.sock.read(4)
+        #print(resp)
+        assert resp[1] == pkt[2] and resp[2] == pkt[3]
+        if resp[3] == 0x80:
+          raise MQTTException(resp[3])
+        return
+
+  # Wait for a single incoming MQTT message and process it.
+  # Subscribed messages are delivered to a callback previously
+  # set by .set_callback() method. Other (internal) MQTT
+  # messages processed internally.
+  def wait_msg(self):
+    res = self.sock.read(1)
+    self.sock.setblocking(True)
+    if res is None:
+      return None
+    if res == b"":
+      raise OSError(-1)
+    if res == b"\xd0":  # PINGRESP
+      sz = self.sock.read(1)[0]
+      assert sz == 0
+      return None
+    op = res[0]
+    if op & 0xf0 != 0x30:
+      return op
+    sz = self._recv_len()
+    topic_len = self.sock.read(2)
+    topic_len = (topic_len[0] << 8) | topic_len[1]
+    topic = self.sock.read(topic_len)
+    sz -= topic_len + 2
+    if op & 6:
+      pid = self.sock.read(2)
+      pid = pid[0] << 8 | pid[1]
+      sz -= 2
+    msg = self.sock.read(sz)
+    self.cb(topic, msg)
+    if op & 6 == 2:
+      pkt = bytearray(b"\x40\x02\0\0")
+      struct.pack_into("!H", pkt, 2, pid)
+      self.sock.write(pkt)
+    elif op & 6 == 4:
+      assert 0
+
+  # Checks whether a pending message from server is available.
+  # If not, returns immediately with None. Otherwise, does
+  # the same processing as wait_msg.
+  def check_msg(self):
+    self.sock.setblocking(False)
+    return self.wait_msg()