python使用MQTT給硬體傳輸圖片

Young5566發表於2019-05-03

任務簡述

最近因需要用python寫一個微服務來用MQTT給硬體傳輸圖片,其中python用的是flask框架,大概流程如下:

image

協議為:
  • 需要將圖片資料封裝成多個訊息進行傳輸,每個訊息傳輸的資料位元組數為1400Byte。
  • 訊息(MQTT Payload) 格式:Web伺服器-------->BASE:
    image
  • 反饋:BASE---------> Web伺服器:
    image
  • 如果Web伺服器傳送完一個“資料傳輸訊息”後,5S內沒有收到MQTT“反饋訊息”或者收到的反饋中顯示“資料包不完整”,則重發該“資料傳輸訊息”。

程式流程圖

根據上面的協議,可以得到如下的流程圖:

image

程式碼如下:

# encoding:utf-8
from flask import Flask, jsonify
from flask_restful import Api, Resource, reqparse
from PIL import Image
from io import BytesIO
import requests
import os, logging, time
import paho.mqtt.client as mqtt
import struct
from flask_cors import *

# 日誌配置資訊
logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(name)s - %(levelname)s - %(message)s  (runing by %(funcName)s',
)


class Mqtt(object):
    def __init__(self, img_data, size):
        self.MQTTHOST = '*******'
        self.MQTTPORT = "******"

        # 訂閱和傳送的主題
        self.topic_from_base = 'mqttTestSub'
        self.topic_to_base = 'mqttTestPub'

        self.client_id = time.strftime('%Y%m%d%H%M%S', time.localtime(time.time()))
        self.client = mqtt.Client(self.client_id)

        # 完成連結後的回掉函式
        self.client.on_connect = self.on_connect
        # 圖片大小
        self.size = size

        # 用於跳出死迴圈,結束任務
        self.finished = None

        # 包的編號
        self.index = 0

        # 將收到的圖片資料按大小分成列表
        self.image_data_list = [img_data[x:x + 1400] for x in range(0, self.size, 1400)]

        # 記錄釋出後的資料,用於監控時延
        self.pub_time = 0


        self.header_to_base = 0xffffeeee
        self.header_from_base = 0xeeeeffff

        # 功能標識
        self.function_begin = 0x01
        self.function_doing = 0x02
        self.function_finished = 0x03

        # 包的完整和非完整狀態
        self.whole_package = 0x01
        self.bad_package = 0x00

        # 頭資訊的格式,小端模式
        self.format_to_base = "<Lbhh"
        self.format_from_base = "<Lbhb"

        # 如果重發包時,用於檢查是否重發第一個包
        self.first = True

        # 如果重發包時,用於檢查是否重發最後一個包
        self.last = False

        self.begin_data = 'image.jpg;' + str(self.size)

    # 連結mqtt伺服器函式
    def on_mqtt_connect(self):
        self.client.connect(self.MQTTHOST, self.MQTTPORT, 60)
        self.client.loop_start()

    # 連結完成後的回撥函式
    def on_connect(self, client, userdata, flags, rc):
        logging.info("+++ Connected with result code {} +++".format(str(rc)))
        self.client.subscribe(self.topic_from_base)

    # 訂閱函式
    def subscribe(self):
        self.client.subscribe(self.topic_from_base, 1)
        # 訊息到來處理函式
        self.client.on_message = self.on_message

    # 接收到資訊後的回撥函式

    def on_message(self, client, userdata, msg):
        # 如果接受第一個包則不需要重發第一個
        self.first = False

        # 將接受到的包進行解壓,得到一個元組
        base_tuple = struct.unpack(self.format_from_base, msg.payload)
        logging.info("+++ imageData's letgth is {}, base_tupe is {} +++".format(self.size, base_tuple))
        logging.info("+++ package_number is {}, package_status_from_base is {}  +++"
                     .format(base_tuple[2], base_tuple[3]))

        # 檢查接受到資訊的頭部是否正確
        if base_tuple[0] == self.header_from_base:
            logging.info("+++ function_from_base is {}  +++".format(base_tuple[1]))

            # 是否完成傳輸,如果完成則退出
            if base_tuple[1] == self.function_finished:
                logging.info("+++  finish work +++")
                self.finished = 1
                self.client.disconnect()
            else:
                # 是否是最後一個包
                if self.index == len(self.image_data_list) - 1:
                    self.publish('finished', self.function_finished)
                    self.last = True
                    logging.info("+++ finished_data_to_base is finished+++")
                else:

                    # 如果接收到的包不是 0x03則進行傳送資料
                    if base_tuple[1] == self.function_begin or base_tuple[1] == self.function_doing:
                        logging.info("+++ package_number is {}, package_status_from_base is {}  +++"
                                     .format(base_tuple[2],base_tuple[3]))

                        # 如果資料的反饋中,包的狀態是1則繼續發下一個包
                        if base_tuple[3] == self.whole_package:
                            self.publish(self.index, self.function_doing)
                            logging.info("+++ data_to_base is finished+++")
                            self.index += 1

                        # 如果資料的反饋中,包的狀態是0則重發資料包
                        elif base_tuple[3] == self.bad_package:
                            re_package_number = base_tuple[2]
                            self.publish(re_package_number-1, self.function_doing)
                            logging.info("+++ re_data_to_base is finished+++")
                        else:
                            logging.info("+++ package_status_from_base is not 0 or 1 +++")
                            self.client.disconnect()
                    else:
                        logging.info("+++  function_identifier is illegal +++")
                        self.client.disconnect()
        else:
            logging.info("+++ header_from_base is illegal +++")
            self.client.disconnect()

    # 資料傳送函式
    def publish(self, index, fuc):
        # 看是否是最後一個包
        if index == 'finished':
            length = 0
            package_number = 0
            data = b''
        else:
            length = len(self.image_data_list[index])
            package_number = index
            data = self.image_data_list[index]

        # 打包資料頭資訊
        buffer = struct.pack(
            self.format_to_base,
            self.header_to_base,
            fuc,
            package_number,
            length
        )
        to_base_data = buffer + data

        # mqtt傳送
        self.client.publish(
            self.topic_to_base,
            to_base_data
        )
        self.pub_time = time.time()

    # 傳送第一個包函式
    def publish_begin(self):
        buffer = struct.pack(
            self.format_to_base,
            self.header_to_base,
            self.function_begin,
            0,
            len(self.begin_data.encode('utf-8')),
        )
        begin_data = buffer + self.begin_data.encode('utf-8')
        self.client.publish(self.topic_to_base, begin_data)

    # 控制函式
    def control(self):
        self.on_mqtt_connect()
        self.publish_begin()
        begin_time = time.time()
        self.pub_time = time.time()
        self.subscribe()
        while True:
            time.sleep(1)
            # 超過5秒重傳
            date = time.time() - self.pub_time
            if date > 5:
                # 是否重傳第一個包
                if self.first == True:
                    self.publish_begin()
                    logging.info('+++ this is timeout first_data +++')

                # 是否重傳最後一個包
                elif self.last == True:
                    self.publish('finished', self.function_finished)
                    logging.info('+++ this is timeout last_data +++')
                else:
                    self.publish(self.index-1, self.function_doing)
                    logging.info('+++ this is timeout middle_data +++')
            if self.finished == 1:
                logging.info('+++ all works is finished+++')
                break

        print(str(time.time()-begin_time) + 'begin_time - end_time')

app = Flask(__name__)
api = Api(app)
CORS(app, supports_credentials=True)

# 接受引數
parser = reqparse.RequestParser()
parser.add_argument('url', help='mqttImage url', location='args', type=str)


class GetImage(Resource):
    # 得到引數並從圖床下載到本地
    def get(self):
        args = parser.parse_args()
        url = args.get('url')
        response = requests.get(url)
        # 獲取圖片
        image = Image.open(BytesIO(response.content))
        # 存取圖片
        add = os.path.join(os.path.abspath(''), 'image.jpg')
        image.save(add)
        # 得到圖片大小
        size = os.path.getsize(add)
        f = open(add, 'rb')
        imageData = f.read()
        f.close()

        # 進行mqtt傳輸
        mqtt = Mqtt(imageData, size)
        mqtt.control()

        # 刪除檔案
        os.remove(add)
        logging.info('*** the result of control is {} ***'.format(1))
        return jsonify({
            "imageData": 1
        })


api.add_resource(GetImage, '/image')

if __name__ == '__main__':
    app.run(debug=True, host='0.0.0.0')

複製程式碼

相關文章