嵌入式系统上多个文件的版本控制



我有一个esp32-wrover-e(16mb闪存和8mb外部ram)我正在用arduino框架进行编码。

在esp32上,有一个Web服务器,它有多个文件用于登录、主页等。我想从node-js服务器远程升级文件系统上的每个文件。固件升级即将完成。

对于固件,有一个对象包含固件版本、更新日期和另外两个阵列。

像这样:

var firmwareInfos = {
version : 1.1,
date : new Date().getTime() / 1000,
downloads: [],
queries: []
};

当esp请求新版本时,它会向服务器执行HTTP GET请求。服务器将此对象发送到esp,它将决定版本是否更大。如果它在服务器上更大,它将下载新固件(通过单独的HTTP请求)。这没关系,而且有效。

现在我想检查文件和他们的版本,像这样。当ESP要求提供固件版本时,服务器也可以将所有文件版本与固件一起发送。

为了实现这一点,服务器将有一个包含所有文件路径及其版本的对象,而esp32也应该在其文件系统中保存该对象。这可能奏效,但无法扩大规模。

如果我想向esp添加新文件和新固件,我必须手动将新文件路径及其版本添加到esp和服务器的版本对象中。

我从服务器端开始使用这个对象:

var versions = {
firmware : {
version : 1.1,
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
main: {
script:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
style:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
index:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
},
login: {
script:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
style:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
index:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
},
admin: {
script:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
style:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
index:{
version : "1.0",
date : new Date().getTime() / 1000,
downloads: [],
queries: []
},
}
};

这将是esp所要求的目标。它还不完整,我还没有完全考虑过,因为正如你所看到的,对象中有多个密钥对,我认为它可以做得简单得多。但是,这是我能想到的最好的方法,而且它不能扩展,因为这个对象也必须存在于esp上,如果我想扩展esp的文件系统,我必须手动向其中添加新文件。

其他解决方案:

esp会循环遍历它的文件系统,收集其中的所有文件名,将其放入json对象中,并将其发送到服务器,请求所有文件的新版本。服务器会循环遍历这个对象,并检查其数据库(mongo-db)中是否有同名对象,然后创建一个新对象,该对象的文件版本与它找到的文件版本相同。为此,我正在考虑这样一种方法:

void fSystem::checkEntireFS(){
SpiRamJsonDocument doc(15000);
JsonArray rootArray = doc.to<JsonArray>();
Serial.println("FS - Start checking entire FS");
gatherFiles("/", 5, rootArray);
serializeJsonPretty(doc, Serial);
Serial.println("FS - End checking entire FS");
}
void fSystem::gatherFiles(const char* dirname, uint8_t levels, JsonArray &rootArray){
File root = LittleFS.open(dirname);
if(!root){
Serial.println("- failed to open directory");
return;
}
if(!root.isDirectory()){
Serial.println(" - not a directory");
return;
}
File file = root.openNextFile();
while(file){
String name = file.name();
JsonObject entryObject = rootArray.createNestedObject();
entryObject["path"] = dirname;
entryObject["name"] = name;
if(file.isDirectory()){
entryObject["type"] = "dir";
if(levels){
gatherFiles(file.path(), levels -1, rootArray);
}
} else {
entryObject["type"] = "file";
entryObject["size"] = file.size();
entryObject["date"] = file.getLastWrite();
}
vTaskDelay(1);
file = root.openNextFile();
}
}

使用ArduinoJson和内置的LittleFS来创建我的文件数组。

这样,我就可以使用getLastWrite()size来确定nodejs服务器上的文件是否更新,而不必依赖于版本号。

我也对版本变量感到困惑。如果我想要三位数的变量,我必须使用一个char数组。如果我使用long或double,我只有一个两位数的可变值,比如1.1。

但是对于char数组,我必须循环遍历char数组并比较每个数字。一定有更好的方法。

所以问题是,什么是最好的方法?你们是如何进行多文件版本控制的?可以做得更简单吗?

感谢您的回答

编辑*

esp32位于远离服务器的防火墙后面的网络中。服务器在公共网络上,但esp不在。esp无法从外部访问,只能从服务器访问。不幸的是,我在esp上的资源非常有限,比如剩下了100kb的空闲堆。我可以从node-js服务器中提取所有文件,并刷新所有文件,无论版本如何,但有一堆文件,每次请求都需要花费大量时间才能下载所有文件。有多个语言文件,每个文件15kb,还有一些图像,每个可以是70-100kb。我只想下载相关的文件,需要更新。

我认为您的动机是进行可扩展/稀疏更新。

如果您真的想继续使用自己的版本控制解决方案char*,可以将其传递给std::string构造函数,然后使用==运算符进行比较。为了检测更改,我会使用文件哈希作为文件版本,但对于合理同步的系统时钟,日期也可以工作。如果你的文件只是从服务器上提取的(从未写入esp),那么你已经知道esp需要预先下载的文件,并避免在esp上使用文件系统代码。也就是说,如果服务器在请求中获得全局版本5,而当前全局版本是8,则发回一个压缩档案,其中包含版本58之间更改的文件。这在延迟和带宽方面是最佳的,您可以在服务器上完成所有繁重的工作。它还将协议本身减少到最低限度。这可以扩展到一种连续的版本控制,只需将最后一次提取时间传递给服务器,服务器就会发回此后更改的文件。

编辑:git不是ESP32 Arduino上的选项

然而,所有这些(以及更多)都包含在git中。我只需要在这两台机器上安装它,并通过您的代码自动调用(node.js上的child_process.exec和c++上的system())。基本上,您需要在服务器上使用git push,在机器人上使用git pull。传输快速且可扩展。您甚至可以在服务器机器上自托管git repository remote,例如,如果您想留在本地网络中。

最终解决方案如下。

在服务器上,管理员可以通过管理员UI将任何类型的文件上传到服务器。(服务器上有esp文件系统结构的精确副本,路径相同)上传成功后,服务器将从该文件创建一个对象,包含其路径、名称和上传日期。

服务器跟踪这些上传,保存并更新在mongoDB中。

当esp请求新文件时,服务器会将此对象提供给esp。esp循环会找到这个对象,并根据对象的路径检查它自己的文件系统中的文件。如果找到该路径,则检查其上次修改的日期。如果日期较低,则将该文件放在一个单独的数组中并继续循环。如果esp在提供的路径上找不到文件,它会假设它是一个新文件,并将此路径也放在单独的数组中。

当它完成循环时,它将要求服务器下载这些位于单独数组中的新文件。这就是我解决服务器多文件更新的方法。

文件上传如下:

var uploader    = require("express-fileupload");
var moduleHandles   = require("./moduleRoutes.js");
function uploadFile(file,path,res,key){
file.mv(`${path}/${file.name}`,function(err){
if(err){
res.status(400).json({status: "error", message: `${file.name} upload failed!`});
}else{
moduleHandles.addNewFileInfo(`${path}/${file.name}`);
res.status(200).json({status: "success", message: `${file.name} upload success!`});
}
});
}
module.exports = {
initPaths: function(app){
app.use(uploader({ createParentPath: true }));
app.post('/fileUpload', function (req, res) {
let path        = req.query.path;
let fileKeys    = Object.keys(req.files);
if( fileKeys.length > 0 ){
fileKeys.forEach(function(key) {
uploadFile(req.files[key],path,res,key);
});
}else{
res.status(400).json({status: "error", message: "Please choose at least one file to be able to upload!"});
}
});
}
}

在模块句柄中,它看起来是这样的:

addNewFileInfo: function(filePath){
// Removing any unnecessary path for the esp
let pathKey = filePath.replace("./HsH_Files","");
let fileInfo = {
dateSec : parseInt(new Date().getTime() / 1000),
name    : pathKey.split("/").pop(),
};
fileInfos[pathKey] = fileInfo;
updateFileInfosInDB();
},
deleteFileInfoByPath: function(filePath){
let elemPath = filePath.replace("./HsH_Files","");
if( fileInfos.hasOwnProperty(elemPath) ){
delete fileInfos[elemPath];
asyncFileInfoUpdateInDB();
}
},

esp方面还没有完成,但看起来像这样:

void pSystem::checkNewFiles(){
HTTPClient http;
char fileCheckURL[200];
http.begin( fileCheckURL );
int httpCode = http.GET();
if (httpCode > 0) {
SpiRamJsonDocument infoJsonDoc(FILE_INFO_SIZE);
DeserializationError error = deserializeJson(infoJsonDoc, http.getStream());
if( !error ){
JsonObject infoDoc = infoJsonDoc.as<JsonObject>();
for (JsonPair infoRef : infoDoc) {
const char* filePath    = infoRef.key().c_str();
JsonObject fileInfo     = infoDoc[filePath];
SpiRamJsonDocument fileDetailsDoc(FILE_DETAILS_SIZE);
JsonObject fileDetails = fileDetailsDoc.to<JsonObject>();
hsh_fileSystem.getFileDetailsOnPath(filePath, fileDetails);
if( fileDetails["result"] ){
if( fileDetails["lastModified"].as<long>() < fileInfo["date"].as<long>() ){
// push it to downloadable files array.
}
}else if( !fileDetails["result"] ){
if( !fileDetails["exists"] ){
hsh_fileSystem.createPath(filePath);
// push it to downloadable files array.
}
}
}
}
}
http.end();
}

最新更新