在物联网(IoT)设备上处理ZIP文件时,经常面临内存限制的挑战。尤其是在使用Arduino框架时,寻找一个能在低内存环境下工作的解决方案尤为困难。本文将介绍如何在ESP32等IoT设备上解压ZIP文件,并探讨其中的技术细节和挑战。
要开始这个项目,需要安装Visual Studio Code (VS Code) 并配置PlatformIO。此外,还需要一个连接好的ESP32设备。在第一次构建之前,需要上传文件系统镜像以设置SPIFFS分区。除非更换ESP32,否则不需要再次执行此操作。
阅读ZIP归档文件相对简单,但也有一些奇特之处,因为许多需要的信息是相对于文件末尾而不是开头的。这使得无法向前流式传输文件。另一个问题是,霍夫曼解码需要能够向后查看32KB的输出缓冲区(是的,已经写入的解压缩数据),因为它使用这些数据进行进一步的解压缩。
这种向后查看的能力使事情变得复杂。最初,实现了一个无缓冲的回看机制,它使用可寻址的输出流而不是RAM中的32KB窗口,但在Arduino File对象中遇到了问题,它根本无法向后寻址。另一个问题是,这种技术要慢得多。
失败后,打算实现一个按需解压缩流,这样就不需要在开始查看之前提取整个流。然而,这样做需要更多的内存。可能会在以后的某个时候选择性地实现这个功能,但这样做会显著增加代码大小。
因此,在所有这些之后,选择了一种方法,它接受一个可读的输入流和一个可写的输出流来运行霍夫曼解压缩操作,使用堆上的临时32KB缓冲区来执行它。真的很讨厌在IoT设备上分配那么多RAM,但除非能找出并解决Arduino File对象的问题,否则别无选择。
由于上述原因,当一切完成后,几乎可以肯定会需要一个附加的SD读写器,除非有像ESP32这样带有SPIFFS分区的东西来存储解压缩的流。
另一个问题是Arduino File对象本身相当占用堆栈,所以即使ZIP归档结构不是,仍然需要将工作对象分配给全局变量或堆上的其他位置。
最后,如果上述没有明确说明,至少需要一个中等重量的IoT设备来运行这段代码。例如,ATMega2560只有8KB的RAM,所以它根本不够。在ESP32WROOM上测试了这段代码,它有512KB的RAM(大约300KB有效可用),这比运行这个程序需要的要多得多,但它是首选平台。
这个库是简短而甜蜜的,这使得使用它非常简单。将在这里做一个代码转储,这应该解释了一切。请记住,这段特定的代码是ESP32特定的:
#include <Arduino.h>
// DID YOU UPLOAD THE FILESYSTEM IMAGE YET?
#include <SPIFFS.h>
#include <stream.hpp>
#include <zip.hpp>
using namespace io;
// for streams
using namespace zip;
// for zips
// if we try to declare all this
// on the stack we run into problems
// apparently the File class is pretty
// heavy:
char path[1024];
File f;
File f2;
archive arch;
archive_entry entry;
void setup() {
Serial.begin(115200);
Serial.println();
SPIFFS.begin(false);
f=SPIFFS.open("/frankenstein.epub", "rb");
if (!f) {
Serial.println("File not found");
while (true);
}
file_stream fs(f);
if (zip_result::success!=archive::open(&fs,&arch)) {
Serial.println("Zip load failed.");
while (true);
}
Serial.print("Number of files ");
Serial.println(arch.entries_size());
arch.entry(11,&entry);
if (SPIFFS.exists("/tmp.htm")) {
SPIFFS.remove("/tmp.htm");
}
f2 = SPIFFS.open("/tmp.htm", "wb");
file_stream fs2(f2);
Serial.print("extracting ");
entry.copy_path(path, 1024);
Serial.print(path);
Serial.println("...");
zip_result rr=entry.extract(&fs2);
if (zip_result::success!=rr) {
Serial.print("extraction failed ");
Serial.println((int)rr);
while (true);
}
Serial.println("extraction complete");
f.close();
f2.close();
f=SPIFFS.open("/tmp.htm", "rb");
if (!f) {
Serial.println("Temp file not found");
while (true);
}
while (true) {
int i = f.read();
if (0 > i)
break;
Serial.write(i);
}
f.close();
}
void loop() {
}
唯一可能令人困惑的是File对象的包装。本质上,zip库不知道ArduinoFile,但它知道io::stream。这就是为什么库可以保持跨平台的原因。它不使用std::iostream<>模板类的原因是因为STL并不总是在每个平台上都完全可用。
另一个要注意的是,使用copy_path()从归档条目中获取路径。IoT库不愿意在堆上动态分配内存,除非绝对必要。它通常会将内存分配留给,这就是它在这里所做的,所以使用它就像使用sprintf()一样,它接受一个缓冲区和一个最大大小。
不要期望它很快。ZIP文件并不是为IoT设备设计的。然而,当需要它时,真的需要它。