2021-12-04 micropython esp32 cam 照相并上传服务器, 参考贴 ,二进制字符串MQTT传输和转换,获取字典键的技巧, 4G MQTT 串口分段传送大文件

参考链接1
参考链接2
参考链接3
参考链接4

写在前面,联网设备和照相模组都是耗能大户,没有良好的供电其他都免谈,在保证供电充足的情况下进行调试~

下载的时候要把特定引脚拉低,参照上面的参考连接。

import camera

#ESP32-CAM(默认配置)
camera.init(0, format=camera.JPEG)


#其他设置:
#上翻下翻
camera.flip(0)
#左/右
camera.mirror(1)

# 分辨率
camera.framesize(camera.FRAME_SVGA)
# 选项如下:
# FRAME_96X96 FRAME_QQVGA FRAME_QCIF FRAME_HQVGA FRAME_240X240
# FRAME_QVGA FRAME_CIF FRAME_HVGA FRAME_VGA FRAME_SVGA
# FRAME_XGA FRAME_HD FRAME_SXGA FRAME_UXGA FRAME_FHD
# FRAME_P_HD FRAME_P_3MP FRAME_QXGA FRAME_QHD FRAME_WQXGA
# FRAME_P_FHD FRAME_QSXGA
# 有关详细信息,请查看此链接:https://bit.ly/2YOzizz

#特效
camera.speffect(camera.EFFECT_NONE)
#选项如下:
# 效果\无(默认)效果\负效果\ BW效果\红色效果\绿色效果\蓝色效果\复古效果
# EFFECT_NONE (default) EFFECT_NEG \EFFECT_BW\ EFFECT_RED\ EFFECT_GREEN\ EFFECT_BLUE\ EFFECT_RETRO

#白平衡
camera.whitebalance(camera.WB_HOME)
#选项如下:
# WB_NONE (default) WB_SUNNY WB_CLOUDY WB_OFFICE WB_HOME

#饱和
camera.saturation(0)
#-2,2(默认为0). -2灰度
# -2,2 (default 0). -2 grayscale 

#亮度
camera.brightness(0)
#-2,2(默认为0). 2亮度
# -2,2 (default 0). 2 brightness

#对比度
camera.contrast(0)
#-2,2(默认为0).2高对比度
#-2,2 (default 0). 2 highcontrast

#质量
camera.quality(10)
#10-63数字越小质量越高

#拍照,buf为jpg二进制数据,可以直接存储为jpg
buf = camera.capture()

摄像头的包装类,这个类是把配置和运行进行了一次包装,每次还会保存更新成一个图片文件。camrun.py

import camera,machine,time
class Camera_run():
    def __init__(self):
        camera.init(0,format=camera.JPEG)
        camera.framesize(camera.FRAME_240X240)
        camera.quality(10)
        self.buf=b''
    def run(self):
        self.buf = camera.capture()
        if len(self.buf)>0:
            print(len(self.buf))
            with open('5.jpg','w') as f:
                f.write(self.buf)
            return self.buf
        else:
            print('没照片数据')
from machine import Pin
# 控制led,esp32cam的自带led引脚为gpio4
led = Pin(4, Pin.OUT)
led.value(1)
time.sleep(1)
led.value(0)

有了摄像头拍的照片,那么一般来说就要传输了,照片这么大采用MQTT方式如下 :

记录:CAM版本的固件更新缓慢,使用人数少,所以固件缺少MQTT功能,尝试了弄回来库放在了/umqtt/simple.py,代码如下

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.server = server
        self.port = port
        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()
        addr = socket.getaddrinfo(self.server, self.port)[0][-1]
        self.sock.connect(addr)
        if self.ssl:
            import ussl

            self.sock = ussl.wrap_socket(self.sock, **self.ssl_params)
        premsg = bytearray(b"\x10\0\0\0\0\0")
        msg = bytearray(b"\x04MQTT\x04\x02\0\0")

        sz = 10 + 2 + len(self.client_id)
        msg[6] = clean_session << 1
        if self.user is not None:
            sz += 2 + len(self.user) + 2 + len(self.pswd)
            msg[6] |= 0xC0
        if self.keepalive:
            assert self.keepalive < 65536
            msg[7] |= self.keepalive >> 8
            msg[8] |= self.keepalive & 0x00FF
        if self.lw_topic:
            sz += 2 + len(self.lw_topic) + 2 + len(self.lw_msg)
            msg[6] |= 0x4 | (self.lw_qos & 0x1) << 3 | (self.lw_qos & 0x2) << 3
            msg[6] |= self.lw_retain << 5

        i = 1
        while sz > 0x7F:
            premsg[i] = (sz & 0x7F) | 0x80
            sz >>= 7
            i += 1
        premsg[i] = sz

        self.sock.write(premsg, i + 2)
        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()

mqtt测试结果为:能用,但是连接比较费力,而且不太稳定,仅仅停留在实验室能用阶段。

下面对MQTT进行简化包装,下面这个类如果在2021年9月份的固件下运行则十分可靠,定时启动每天24次连续运行20天无故障。CAM版本自己复制的库下面就连接比较困难,连上就能用,多重启几次也马马虎虎吧。

from umqtt.simple import MQTTClient
import time 
class Mqtt_run():
    def __init__(self,dev_name,ip,name_id,password,list_sub):# 设备名 , 服务器地址,端口 , 账号, 密码,订阅列表
        self.mqtt_mast=MQTTClient(dev_name,ip,1883,name_id,password,keepalive=60)
        print('mqtt开始')
        while 1:
            try:
                self.mqtt_mast.connect()
            except:
                print('mqtt失败')
                continue
            break
        print('mqtt开始')
        self.mqtt_mast.set_callback(self.recdate1)# 绑定回调函数,名字别错
        for i in list_sub:
            self.mqtt_mast.subscribe(i,qos=1)#设置订阅的主体,这里是123
    def recdate1(self,t,m):#这是回调函数,有信息并触发后都在这里执行
      ###############我就是填充业务逻辑的地方###############
      #print("我在这里运行",t,m)
      pass
      ##################################################
if __name__=='__main__':
    #from mqtt1 import Mqtt_run
    a=Mqtt_run('cam','ip','esp32','esp32',['123','456']) #设备名 , 服务器地址,端口 , 账号, 密码,订阅列表
    a.mqtt_mast.check_msg() #轮询消息,主函数中周期越快越好,没这个就听不叫了
    a.mqtt_mast.publish('456','fffad') # *****前边是发往哪个主题,后面是内容 发送数据*****************************
    time.sleep(5) #延时,别刷屏

摄像头部分总体可以,初始化只能执行一次,重复执行报错或者重启,也是固件写的质量不高造成的,也是基本能用但是不十分可靠的状态,芯片是ESP32 其他成熟功能没有问题

发送MQTT需要先连接网络,所以本次直接借用了我之前写的日志配网系统里边的程序。日志配网传送门

有一个需要强调的地方就是MQTT传输的时候一般都是JSON格式,而图片是二进制格式,所以要在JSON封包前对二进制进行字符串转换,发过去了还要解回二进制,所以下面程序里边有binascii内容

from test import Camera_run #摄像头
from loggers import Wlan_clock_log_run #联网 和 日志
from mqtt1 import Mqtt_run #mqtt包装类
import machine,json,binascii,time
try:
    a=Camera_run()
except:
    machine.reset()
b=a.run()

wcl=Wlan_clock_log_run('300king','密码','esp32') #WIFI账号 wifi密码 自定日志名称
wcl.wlan_run() #启动联网,带线程断线重连
wcl.clock_run() #启动时钟同步,并配置日志静态内容
logg=wcl.loggers_run() #实例化日志
logg.info('ccc')   #日志内容写入

def json_data(name,data): #把二进制转成base64然后JSON封包,前面是字典名后面是BASE64后的内容
    xx=dict()
    xx[name]=binascii.b2a_base64(data)
    j_xx=json.dumps(xx)
    return j_xx
qtt=Mqtt_run('cam','IP','esp32','esp32',['123','456']) #设备名 , 服务器地址,端口 , 账号, 密码,订阅列表
while 1:
    
    #qtt.mqtt_mast.check_msg() #轮询消息,主函数中周期越快越好,没这个就听不叫了
    qtt.mqtt_mast.publish('456',json_data('jpg',b)) # *****前边是发往哪个主题,后面是内容 发送数据***
    time.sleep(20)
    b=a.run()

接收服务器如何转成图片文件呢?如下pc端的解包存图程序~~~这里有base64转二进制部分,以及判断字典键的技巧

import paho.mqtt.client as mqtt
import json,binascii
def on_connect(client, userdata, flags, rc):
    print("Connected with result code: " + str(rc))

def on_message(client, userdata, msg):
    print(msg.topic + " " + str(msg.payload))
    print(msg.payload)
    data_json=json.loads(msg.payload)#第一步,JSON解包
    if list(data_json.keys())[0]=='jpg': #第二部  列表化第0个数据是‘JPG’判断下是不是图片数据
        
        datas=binascii.a2b_base64(data_json['jpg'])#把base64转成二进制
        with open('5.jpg','wb') as f:#保存文件
            f.write(datas)
    elif list(data_json.keys())[0]=='text':
        print('text')
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set('esp32', password='esp32')  #这里也是:需要验证账号密码就带上这句,准许匿名就不带这句
client.connect('ip', 1883, 60) # 600为keepalive的时间间隔
client.subscribe('456', qos=0)#前边是主题
client.loop_forever() # 阻塞并保持连接

总体完成后ESP32 的主程序中只需要加上看门狗定时器,周期拍照这个工作马马虎虎也算能够完成,出错了卡住了跑累了就重启~也算是能够使用,更新个图片这么个任务也还凑合。


两天后:一直以来对这个摄像头残念不断,测试后觉得板子自己跑MQTT可靠性不佳,同时依赖WIFI覆盖而有WIFI的地方基本就是居民区了,作为嵌入式设备属实尴尬。就为这俩点都应该弃用CAM,

但是这两天换个思路,采用4G的DTU MQTT模块传送图片,于是在工具盒里翻出找出一块DTU mqtt模块,飞思创的704或者是724,这个块把MQTT当串口用。稳定性还是基本可以的,起码比ESP32后导入UMQTT的靠谱的多。

首先供电必须外接

2021-12-04 micropython esp32 cam 照相并上传服务器, 参考贴 ,二进制字符串MQTT传输和转换,获取字典键的技巧, 4G MQTT 串口分段传送大文件_第1张图片
2021-12-04 micropython esp32 cam 照相并上传服务器, 参考贴 ,二进制字符串MQTT传输和转换,获取字典键的技巧, 4G MQTT 串口分段传送大文件_第2张图片

这个4G模块传数据很方便,配置好就能用,传图片的时候倒有些问题:图片很大,mqtt模块自己对数据进行分割,也就是说一次传送不完。分成几次给你传完,但是PC端的MQTT是多线程的,你分开了。。。那他就分开接收了,数据不完整。于是~简单的串口开始进行文件切片操作,而对面PC端开始多线程拼接操作。付出10根毫毛阵亡的代价,勉强用延时写了个慢速的传图片两端,为了节约毛发没有继续优化。

cam端

from camrun import Camera_run
import machine,json,binascii,time
import machine
from machine import UART
from machine import Pin
# 控制led,esp32cam的自带led引脚为gpio4
led = Pin(4, Pin.OUT)
u1=UART(1,115200,rx=13,tx=14,txbuf=30000,rxbuf=30000)
en = machine.Pin(15,machine.Pin.OUT,machine.Pin.PULL_UP)
try:
    a=Camera_run()
except:
    machine.reset()
led.value(1)
bmp=a.run()
en.value(1)
led.value(0)

class File_split(): #大文件切片串口发送类,主要靠延时来控制顺序,留了顺序号,但是发现没啥问题就没有写自动排序
    def __init__(self,filename):
        import json,binascii
        self.filename=filename #二进制数据
        self.part = len(filename)//1000   #2000字节一份看看有几份
        self.other = len(filename)%1000
        if self.other !=0:               #如果有余数就多分一份
            self.part =self.part+1
            
    def split(self):
        u1.write(json.dumps(['start',-1,'asdf'])) #启动 
        time.sleep(0.5)
        u1.read()#清空串口
        for i in range(self.part):
            while 1 :
                if u1.any()>0: # 服务器有送来的数据?
                    rec_data = u1.read()
                    print(rec_data)
                    if rec_data==b'data_ok': #数据是正确的那就跳出循环发送下一条,不对就循环重发
                        print('xxx')
                        break
                date=json.dumps(['jpg',i,binascii.b2a_base64(self.filename[0+i*1000:1000+i*1000])]) #数据分段 
                u1.write(date)
                time.sleep(0.8)
                print('3333')
                    
                
        time.sleep(1)  
        u1.write(json.dumps(['sendok',self.part,'asdf'])) #结束
        
a=File_split(bmp) # 实例化大文件切片类,bmp是要发送的二进制数据
a.split() #执行切片并发送

# while 1:
#     

pc端

import paho.mqtt.client as mqtt
import json,binascii,datetime,time
def on_connect(client, userdata, flags, rc):
    print("Connected with result code: " + str(rc))

def on_message(client, userdata, msg):
    #print(msg.topic + " " + str(msg.payload))
    #print(msg.payload)
    try :
        data=json.loads(msg.payload)
        print(data[0],data[1],len(data[2])) ##测试用
        global data_all
        if data[0]==   'start':  #启动清空,
            print('启动')##测试用
            data_all = b''
        elif data[0]==   'sendok':  #发送完保存
            print('保存')##测试用
            with open('5.jpg','wb') as f:
                f.write(data_all)
        elif data[0]==   'jpg': #发送中缓存
            
            data_all=data_all+binascii.a2b_base64(data[2])
            print(len(data_all))##测试用
        client.publish('pctocam', payload='data_ok', qos=0)  #不出错就回传个'data_ok'  
    except:
        print('errr')
        client.publish('pctocam', payload='data_err ', qos=0)#出错就回传个'err'  

data_all=b''
client = mqtt.Client()
client.on_connect = on_connect
client.on_message = on_message
client.username_pw_set('esp32', password='esp32')  #这里也是:需要验证账号密码就带上这句,准许匿名就不带这句
client.connect('ip', 1883, 60) # 600为keepalive的时间间隔
client.subscribe('camtopc', qos=0)#前边是主题
client.loop_start()
while 1 :
    time.sleep(1)

总结,还行,还行~

你可能感兴趣的:(micropython,python)