今天再为我的esp32cam增加功能: 在订阅端能够获得sd卡的内容
读取sd卡根目录内容
#include <SD_MMC.h>
bool setupSDCard() {
if (!SD_MMC.begin("/sdcard", true)) { //挂载点要这样,直接斜杠会报错
Serial.println("SD Card Mount Failed");
return false;
}
uint8_t cardType = SD_MMC.cardType();
if (cardType == CARD_NONE) {
Serial.println("No SD card attached");
return false;
}
return true;
}
// ...fongdan.com
Serial.println("sdcard");
if (!setupSDCard()) {
Serial.println("err");
} else {
Serial.println("success");
File root = SD_MMC.open(doc["data"]["name"].as<String>().c_str()); //参数也可以"/",但这样可操作性就没那么好,只能是看根目录的文件;我这种方式可以根据订阅端来切换目录
if (!root) {
Serial.println("open error");
return;
}
File entry = root.openNextFile();
while (entry) {
delay(500);
//fongdan' com start 下面的代码是我使用了PubSubClient将文件名发送到订阅方
char message[200];
snprintf(message, 200, "{\"action\":\"sdcard_data_c\",\"data\":{\"is_dir\":%d,\"name\":\"%s\"}}", entry.isDirectory(),entry.name());
client.publish(topicset, message);
// end
entry = root.openNextFile();
}
Serial.println("sdcard end");
SD_MMC.end();
}
在读取的时候可能出现读取不了,root.openNextFile()直接是空的情况;但是我也不知道是什么问题,开始一直调试不行,直到我做了以下的事情(不知道是不是这个玄学问题):
- 拔掉内存卡插手机里,然后fongdan删com除了所有文件
- 重新插入,重新写入以下代码到esp32cam
#include <SD_MMC.h>
void setup() {
Serial.begin(115200);
while (!Serial) {
; // 等待串口监视器打开
}
if (!SD_MMC.begin("/sdcard", true)) {
Serial.println("初始化SD卡失败");
} else {
Serial.println("SD卡初始化成功");
File myFile = SD_MMC.open("/myfile.txt", FILE_WRITE);
if (!myFile) {
Serial.println("无法创建文件");
return;
}
Serial.println("文件创建成功");
myFile.println("这是一个简单的文本文件。");
myFile.close();
File root = SD_MMC.open("/");
if (!root) {
Serial.println("open error");
return;
}
File entry = root.openNextFile();
while (entry) {
Serial.println(entry.name());
entry = root.openNextFile();
}
Serial.println("sdcard end");
}
}
void loop() {
// 这里不需要rorinliang076 at gmail循环
}
其实可能还是电源不够标准都有可能的
经过以上操作之后,订阅端解析接受到的json就可以处理了,我的做法是push到一个数组,vue的for循环出来就可以
如何返回文件内容?
#define CHUNK_SIZE 256
#include <SD_MMC.h>
const char* topicset = "xxx/set";
//...
if (setupSDCard()) {
const char* filePath = doc["data"]["name"];//这个是订阅端传递的文件路径
File file = SD_MMC.open(doc["data"]["name"].as<String>().c_str());
if (!file) {
Serial.println("Failed to open file for reading");
return;
}
size_t fileSize = file.size();
//分段发送文件数据
byte buffer[CHUNK_SIZE];
size_t offset = 0;
while (offset < fileSize) {
size_t bytesToRead = min(fileSize - offset, static_cast<size_t>(CHUNK_SIZE));
size_t bytesRead = file.read(buffer, bytesToRead);
if (bytesRead != bytesToRead) {
Serial.println("Error reading file");
readyread = false;
break;
}
if (!client.publish(topicset, buffer, bytesRead,false)) {
Serial.println("Failed to publish data");
readyread = false;
break;
}
offset += bytesRead;
}
file.close();
//发送一个结束读取的状态
char message[200];
snprintf(message, 200, "{\"action\":\"sdcard_read_end\",\"data\":{\"name\":\"%s\"}}", filePath);
client.publish(topicset, message);
}
SD_MMC.end();
client是PubSubClient实例
接下来是客户端接收
客户端使用的是mqtt.min.js;具体如何订阅:
//...
let host = "wss://bemfa.com:9504/wss";
client = mqtt.connect(host, {
keepalive: 60,
clientId: '',
protocolId: 'MQTT',
protocolVersion: 4,
clean: true,
reconnectPeriod: 1000,
connectTimeout: 30 * 1000,
});
// ...
//在监听函数message中:
client.on('message', (topic, message, packet) => {
if(!packet.retain){ /*普通数据*/
try {
let data = message.toString();
let jsond = JSON.parse(data);
console.log(jsond)
if (jsond.action == 'sdcard_data_c') {//文件列表
_this.files.push(jsond.data)
} else if (jsond.action == 'sdcard_read_end') {//文件接收结束
}
} catch (e) {
console.log(e)
}
}else{ /*二进制数据*/
if (packet.payload.length > 0) {
console.log('文件数据');
try{
_this.chunks.push(packet.payload);//uint8Array
// 处理二进制
if (_this.blob_link) {
URL.revokeObjectURL(_this.blob_link);
}
let blob = new Blob(_this.chunks, {
type: 'application/octet-stream'
});
let url = URL.createObjectURL(blob);
_this.blob_link = url;
console.log(_this.blob_link);
}catch(e){
//TODO handle the exception
}
}
}
})
_this.blob_link就是接收二进制之后处理得出的本地文件路径,其实处理二进制这部分应该是在sdcard_read_end中处理;这里仅供测试因为文件比较小一下就传递过来了
以上就是esp32cam远程读取sd卡的主要过程,具体代码如有需要可以联系< rorinliang076 at>gmail dian com
2024年6月24日 09点30分 fongdan .ccom
测试到如果是一个简单的txt文件,几百字节一下就传递过来了;而且没有任何问题;但是如果是一个几千字节的图片就不行。经过查看PubSubClient.h以及PubSubClient.cpp源码,发现可以设置消息体的总字节限制: public: boolean setBufferSize(uint16_t size)这个函数,将它设置大些就可以了;
还有使用 boolean PubSubClient::publish(const char topic, const uint8_t payload, unsigned int plength, boolean retained)** 来发送二进制数据[rorinliang076@gm com]的时候还会检查BufferSize和消息体字节长度的大小;如果消息体的长度超过了BufferSize,那么publish会直接返回false;
// PubSubClient.cpp
//...
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) {
if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) {
// Too long
return false;
}
// ...
}
(fongdan.com]bufferSize就是setBufferSize设置的,默认是256字节;MQTT_MAX_HEADER_SIZE默认是5,strnlen这段是返回topic的字符长度(指定最大的长度是this->bufferSize),还有发送的payload的字节大小(plength);因此发送的数据和消息体必须符合条件,否则无法发送;
这个函数在有错误的时候因为直接返回bool(还有其余代码也会返回bool),因此可以修改一下源码,让他可以输出:
//PubSubClient.h
//...
typedef void (*ErrorCallback)(const char* error);
class PubSubClient : public Print {
private:
ErrorCallback errorCallback;
//...
public:
void setErrorCallback(ErrorCallback callback);
boolean publish(const char* topic, const uint8_t * payload, unsigned int plength, boolean retained);
}
//PubSubClient.cpp
void PubSubClient::setErrorCallback(ErrorCallback callback) {
this->errorCallback = callback;
}
boolean PubSubClient::publish(const char* topic, const uint8_t* payload, unsigned int plength, boolean retained) {
if (connected()) {
if (this->bufferSize < MQTT_MAX_HEADER_SIZE + 2+strnlen(topic, this->bufferSize) + plength) {
// Too long rorinliang076 gm
if (errorCallback) errorCallback("Error: Message too long for buffer");
return false;
}
if (errorCallback) {
int message_length = 120;
char message[message_length];
snprintf(message, message_length,"--bufferSize:%d;MQTT_MAX_HEADER_SIZE:%d;topic:%d;plength:%d;",this->bufferSize,MQTT_MAX_HEADER_SIZE,strnlen(topic, this->bufferSize),plength);
errorCallback(message);
}
// Leave room in the buffer for header and variable length FONGdan DOT COM field
uint16_t length = MQTT_MAX_HEADER_SIZE;
length = writeString(topic,this->buffer,length);
// Add payload fongdan com
uint16_t i;
for (i=0;i<plength;i++) {
this->buffer[length++] = payload[i];
}
// Write the header
uint8_t header = MQTTPUBLISH;
if (retained) {
header |= 1;
}
boolean result = write(header,this->buffer,length-MQTT_MAX_HEADER_SIZE);
if (!result) {
if (errorCallback) errorCallback("Error: Failed to write MQTT message");
}
return result;
}
if (errorCallback) errorCallback("Error: Not connected to MQTT broker");
return false;
}
//arduino代(rorinliang076)at gm码中
void errorHandler(const char* error) {
Serial.println(error);
}
void setup() {
client.setErrorCallback(errorHandler);
}
这样在arduino调试的时候就可以看到publish时候的错误提示了