1、环境配置及抓图
基础部分请参考前序几篇文章。
2、esp32-cam通讯串口定义
esp32-cam折腾了很久串口方式,在arduino直接调用即可。
可以设置任何可用的pin作为rx、tx,我们选择定义针脚13,12
image.png
image.png
先定义串口
HardwareSerial swSer(1);
然后初始化针脚即可,rx 13,tx 12,对应gpio13、gpio12
swSer.begin(115200,SERIAL_8N1,13,12);
即可使用串口
经过多次测试,esp32-cam用自定义针脚会引起图片数据乱码,故需采用默认的rx gpio3、tx gpio1通讯,找到原因花了将近7-8个小时,刷了3、40遍才偶然发现,居坑。
3、sim7020 nbiot
image.png
开发板已经封装,直接at指令即可。
参考地址:http://www.waveshare.net/wiki/SIM7020C_NB-IoT_HAT
esp32-cam和sim7020用rx和tx 接tx1和rx1就可以通讯。
需要sim7020后启动才不会串口乱码。
4、sim7020 mqtt相关at指令
跟mqtt服务器建立连接,1024经测试是可定义缓冲的最大长度。超出会报错。
AT+CMQNEW="IP地址","端口号",12000,1024
连接mqtt服务器,esp32是设备名称,最好用mac地址或者是sim卡号码或其他唯一标记。
AT+CMQCON=0,3,"esp32",600,0,0
发布主题,1000是长度,必须跟字符长度对应,1000是sim7020 at指令 mqtt语句能够接受的最大长度。
test为主题名称,可用主题/唯一标记的方式分开同一图片的不同序列。可以标记最大值和传续值;
AT+CMQPUB=0,"test",1,0,0,1000,"8246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE30B8868EF4C4390FCC2AE8A48246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE30B8868EF4C4390FCC2AE8A48246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE30B8868EF4C4390FCC2AE8A48246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE30B8868EF4C4390FCC2AE8A48246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE30B8868EF4C4390FCC2AE8A48246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE30B8868EF4C4390FCC2AE8A48246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE30B8868EF4C4390FCC2AE8A48246D69FA2EA77508915E519ABBFF0008CEA3DCB9F7C55DA1FCC47335D069F0E6A27F8E61F5A4FF00847750C60CF2FE1472C7BFE"
关闭mqtt连接
AT+CMQDISCON=0
5、获取esp32-cam默认mac地址
计划采用mac地址作为设备唯一码,在mqtt中标出.
https://qiita.com/tapinu00/items/9fa3d7b0ba6d8b44da08
查资料发现日本对于物联网的应用要更深入和细致,文章写的非常好。
image.png
Serial.begin(115200);
Serial.println("-----------------------------");
uint64_t chipid;
chipid=ESP.getEfuseMac();//The chip ID is essentially its MAC address(length: 6 bytes).
Serial.printf("ESP32 Chip ID = %04X\r\n",(uint16_t)(chipid>>32));//print High 2 bytes
Serial.printf("Chip Revision %d\r\n", ESP.getChipRevision());
esp_chip_info_t chip_info;
esp_chip_info(&chip_info);
Serial.printf("Number of Core: %d\r\n", chip_info.cores);
Serial.printf("CPU Frequency: %d MHz\r\n", ESP.getCpuFreqMHz());
Serial.printf("Flash Chip Size = %d byte\r\n", ESP.getFlashChipSize());
Serial.printf("Flash Frequency = %d Hz\r\n", ESP.getFlashChipSpeed());
Serial.printf("ESP-IDF version = %s\r\n", esp_get_idf_version());
Serial.printf("Free Heap Size = %d\r\n", esp_get_free_heap_size());
Serial.printf("System Free Heap Size = %d\r\n", system_get_free_heap_size());
Serial.printf("Minimum Free Heap Size = %d\r\n", esp_get_minimum_free_heap_size());
Serial.println();
uint8_t mac0[6];
esp_efuse_mac_get_default(mac0);
Serial.printf("Default Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac0[0], mac0[1], mac0[2], mac0[3], mac0[4], mac0[5]);
uint8_t mac1[6];
esp_efuse_read_mac(mac1);
Serial.printf("EFuse Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac1[0], mac1[1], mac1[2], mac1[3], mac1[4], mac1[5]);
uint8_t mac2[6];
system_efuse_read_mac(mac2);
Serial.printf("System Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac2[0], mac2[1], mac2[2], mac2[3], mac2[4], mac2[5]);
uint8_t mac3[6];
esp_read_mac(mac3, ESP_MAC_WIFI_STA);
Serial.printf("[Wi-Fi Station] Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac3[0], mac3[1], mac3[2], mac3[3], mac3[4], mac3[5]);
uint8_t mac4[7];
esp_read_mac(mac4, ESP_MAC_WIFI_SOFTAP);
Serial.printf("[Wi-Fi SoftAP] Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac4[0], mac4[1], mac4[2], mac4[3], mac4[4], mac4[5]);
uint8_t mac5[6];
esp_read_mac(mac5, ESP_MAC_BT);
Serial.printf("[Bluetooth] Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac5[0], mac5[1], mac5[2], mac5[3], mac5[4], mac5[5]);
uint8_t mac6[6];
esp_read_mac(mac6, ESP_MAC_ETH);
Serial.printf("[Ethernet] Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac6[0], mac6[1], mac6[2], mac6[3], mac6[4], mac6[5]);
代码运行
[13:55:57.088] -----------------------------
ESP32 Chip ID = A8F6
Chip Revision 1
Number of Core: 2
[13:55:57.097] CPU Frequency: 240 MHz
Flash Chip Size = 4194304 byte
Flash Frequency = 80000000 Hz
ESP-IDF version = v3.3-beta1-179-ge931fe9f5-dirty
Free Heap Size = 292120
System Free Heap Size = 292120
Minimum Free Heap Size = 286036
Default Mac Address = 3C:71:BF:C7:F6:A8
EFuse Mac Address = 3C:71:BF:C7:F6:A8
System Mac Address = 3C:71:BF:C7:F6:A8
[Wi-Fi Station] Mac Address = 3C:71:BF:C7:F6:A8
[Wi-Fi SoftAP] Mac Address = 3C:71:BF:C7:F6:A9
[Bluetooth] Mac Address = 3C:71:BF:C7:F6:AA
[Ethernet] Mac Address = 3C:71:BF:C7:F6:AB
采用esp_efuse_mac_get_default获取值作为设备唯一值;
6、核心代码
总共花200多行代码搞通。
注:设置5位随机数作为每次上报图片的识别码。
标有作业的地方留意。
//加载基本库
#include <Arduino.h>
//esp32-cam库,所有拍照相关定义
#include <esp_camera.h>
//变量定义----------------------------bigen
//esp32-cam针脚定义 拍照相关
constexpr int kCameraPin_PWDN = 32;
constexpr int kCameraPin_RESET = -1; // NC
constexpr int kCameraPin_XCLK = 0;
constexpr int kCameraPin_SIOD = 26;
constexpr int kCameraPin_SIOC = 27;
constexpr int kCameraPin_Y9 = 35;
constexpr int kCameraPin_Y8 = 34;
constexpr int kCameraPin_Y7 = 39;
constexpr int kCameraPin_Y6 = 36;
constexpr int kCameraPin_Y5 = 21;
constexpr int kCameraPin_Y4 = 19;
constexpr int kCameraPin_Y3 = 18;
constexpr int kCameraPin_Y2 = 5;
constexpr int kCameraPin_VSYNC = 25;
constexpr int kCameraPin_HREF = 23;
constexpr int kCameraPin_PCLK = 22;
//定义mqtt服务器ip和端口,ip需要用自己指定域名
extern const char *kMqttServerAddress = "XX.XX.XX.XX";
extern const uint16_t kMqttServerPort = 1883;
//设备mac地址
//char *Mac_Address;
char Mac_Address[12] = "";
//变量定义--------------------------------end
//函数定义----------------------------bigen
//初始化相机
void setupCamera() {
const camera_config_t config = {
.pin_pwdn = kCameraPin_PWDN,
.pin_reset = kCameraPin_RESET,
.pin_xclk = kCameraPin_XCLK,
.pin_sscb_sda = kCameraPin_SIOD,
.pin_sscb_scl = kCameraPin_SIOC,
.pin_d7 = kCameraPin_Y9,
.pin_d6 = kCameraPin_Y8,
.pin_d5 = kCameraPin_Y7,
.pin_d4 = kCameraPin_Y6,
.pin_d3 = kCameraPin_Y5,
.pin_d2 = kCameraPin_Y4,
.pin_d1 = kCameraPin_Y3,
.pin_d0 = kCameraPin_Y2,
.pin_vsync = kCameraPin_VSYNC,
.pin_href = kCameraPin_HREF,
.pin_pclk = kCameraPin_PCLK,
.xclk_freq_hz = 20000000,
.ledc_timer = LEDC_TIMER_0,
.ledc_channel = LEDC_CHANNEL_0,
// 图片格式
.pixel_format = PIXFORMAT_JPEG,
// 图片尺寸 可根据esp_camera.h内容自行修改
.frame_size = FRAMESIZE_SVGA,
// 图片质量 可根据esp_camera.h内容自行修改
.jpeg_quality = 10,
.fb_count = 1,
};
//初始化配置
esp_err_t err = esp_camera_init(&config);
// Serial.printf("esp_camera_init: 0x%x\n", err);
// sensor_t *s = esp_camera_sensor_get();
// s->set_framesize(s, FRAMESIZE_QVGA);
}
//初始化串口
void setupSerial() {
// 初始化默认串口
Serial.begin(115200);
// 因为采用默认串口和nb_iot模块通讯,可以另外用软串口输出调试信息;
}
//获取默认mac地址作为设备标示
void getMacAddress() {
uint8_t mac0[6];
// 获取默认mac地址
esp_efuse_mac_get_default(mac0);
// Serial.printf("Default Mac Address = %02X:%02X:%02X:%02X:%02X:%02X\r\n", mac0[0], mac0[1], mac0[2], mac0[3], mac0[4], mac0[5]);
// 把mac地址转成字符串
sprintf (Mac_Address, "%02X%02X%02X%02X%02X%02X", mac0[0], mac0[1], mac0[2], mac0[3], mac0[4], mac0[5]);
}
//连接mqtt服务器
void initMqtt() {
// 发送at判断通讯设备状态
Serial.println("AT\r\n");
delay(1000);
// 作业:需要根据at返回状态判断是否执行下一步,如果连不上需要重启,重启采用 ESP.restart()命令
// 建立mqtt通道
Serial.println("AT+CMQNEW=\"" + String(kMqttServerAddress) + "\",\"" + kMqttServerPort + "\",12000,1024\r\n");
delay(1000);
// 在mqtt服务器注册设备
Serial.println("AT+CMQCON=0,3,\"" + String(Mac_Address) + "\",600,0,0\r\n");
delay(1000);
}
//发送消息(拍照数据,图片长度)
void sendMqttMsg(uint8_t *data, int len) {
//思路:把uint8格式数据转成16进制数据,然后分批发送,uint8数据长度和16进制字符数据长度有差异(重点)
// 定义当前传输批次标记,采用随机5位数字,区分每天不同批次
int batch = random(10000, 99999);
//上报数据
String msg;
//定义临时取数据存储
char m[1024] = "";
//传送计数
int timeCount = 0;
// 计算总传输次数
int tranCount = (len * 2 + (1000 - 1)) / 1000;
// 总字符数量,临时输出
// Serial.printf("total: %d\n", len * 2);
// 从uint8数据堆栈中逐一取出数据转成16进制,并发送,逢内容够1000发送
for (int i = 0; i < len; i++) {
// 堆栈取数据,转16进制,不足2位左边补0
sprintf (m, "%02X", data[i]);
// 逐一累加到传送字段中
msg += m;
// 传送数据达到1000即发送清空数据队列,最后一个队列不足需要判断
if (msg.length() == 1000) {
// 传送计数
timeCount += 1;
// 传送数据,主题可以自定义,示例格式为
Serial.print( "AT+CMQPUB=0,\"lamp_iot/" + String(Mac_Address) + "/" + String(batch) + "_" + String(tranCount) + "_" + String(timeCount) + "\",1,0,0,1000,\"" + msg + "\"\r\n");
// 清空队列
msg = "";
// nb_iot设备at指令有延时,此处需要有delay处理
delay(500);
// 作业:传输不成功需要处理
}
}
// 最后一段数据可能不足1000字符传送门
if (msg.length() > 0) {
// 传送数据,主题可以自定义,示例格式为
Serial.print( "AT+CMQPUB=0,\"lamp_iot/" + String(Mac_Address) + "/" + String(batch) + "_" + String(tranCount) + "_" + String(tranCount) + "\",1,0,0," + msg.length() + ",\"" + msg + "\"\r\n");
// 清空队列
msg = "";
// nb_iot设备at指令有延时,此处需要有delay处理
delay(500);
// 作业:传输不成功需要处理
}
}
//断开连接
void disconnectMqtt() {
// 断开mqtt连接
Serial.println("AT+CMQDISCON=0\r\n");
delay(1000);
}
//函数定义--------------------------------end
//主程序----------------------------bigen
//初始化
void setup() {
// 初始化串口
setupSerial();
delay(1000);
// 获取mac地址
getMacAddress();
delay(1000);
// 初始化相机
setupCamera();
delay(1000);
}
//主循环函数
void loop() {
// 连接mqtt
initMqtt();
// 抓拍图片
camera_fb_t *fb = esp_camera_fb_get();
// 抓拍成功进行处理,作业:不成功需要触发重启
if ( fb ) {
// Serial.printf("width: %d, height: %d, buf: 0x%x, len: %d\n", fb->width, fb->height, fb->buf, fb->len);
// 捕获图片成功,开始上报
sendMqttMsg(fb->buf, fb->len);
}
//断开mqtt服务
disconnectMqtt();
// 间隔5分钟上报一次,此处定时可自行定义时间,单位为毫秒ms;
delay(300000); // [ms]
}
//主程序--------------------------------end
写代码感悟,逻辑思路要清晰,变量定义明确,函数定义明确,逻辑简洁准确,强迫症要格式对齐。
感悟技术的艺术之美。
7、接收mqtt数据效果
一张图片拆了80多个包,一分多钟传输完成。
image.png
可以用后端语言接受每个设备上报的数据,把数据包根据规则组包生成图片即可。
下列是通过python直接把16进制转成jpg图片,手工版数据拼起来图片效果如图:(夜晚阴影中拍摄,看得到黑色数据线和凳子一脚)
image.png
至此通过nb_iot设备跑通。