Skip to content

Commit

Permalink
NVS Partition editor (#246)
Browse files Browse the repository at this point in the history
* initial panel webview implementation

* add webpack entry

* add header component extension cmd

* initial table definition

* csv json parser write file

* add generate nvs partition cmd

* add docs and validation
  • Loading branch information
brianignacio5 authored Dec 30, 2020
1 parent 47ee387 commit c90ffde
Show file tree
Hide file tree
Showing 19 changed files with 1,210 additions and 6 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,6 +123,7 @@ Click <kbd>F1</kbd> to show Visual studio code actions, then type **ESP-IDF** to
| Search in documentation... | <kbd>⌘</kbd> <kbd>E</kbd> <kbd>D</kbd> | <kbd>Ctrl</kbd> <kbd>E</kbd> <kbd>D</kbd> |
| Install ESP-ADF | | |
| Install ESP-MDF | | |
| Open NVS Partition Editor | | |

The **Add Arduino ESP32 as ESP-IDF Component** command will add [Arduino ESP32](https://github.com/espressif/arduino-esp32) as a ESP-IDF component in your current directory with in `${CURRENT_FOLDER}/components/arduino`. You can also use **Create ESP-IDF project** with the `arduino-as-component` template to create a new project folder that includes arduino as ESP-IDF component.

Expand Down
25 changes: 25 additions & 0 deletions docs/NVS_PARTITION_EDITOR.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# NVS Partition Editor UI for ESP-IDF

Our VS Code Extension comes with UI to creates a binary file based on key-value pairs provided in a CSV file. The resulting binary file is compatible with NVS architecture defined in [ESP_IDF Non Volatile Storage](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_flash.html). The expected CSV format is:

```
key,type,encoding,value <-- column header (must be the first line)
namespace_name,namespace,, <-- First entry must be of type "namespace"
key1,data,u8,1
key2,file,string,/path/to/file
```

This is based on ESP-IDF [NVS Partition Generator Utility](https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/storage/nvs_partition_gen.html). Make sure `espIdfPath` configuration setting is defined for this to properly work.

## Prerequisites

- ESP-IDF `>=v4.x`
- IDF VSCode extension version `>=0.6.0`

## Steps

- Press F1 -> `Type ESP-IDF: Open NVS Partition Editor` (to create a new file) or right click an existing CSV file.
- If creating a new file, choose the name of the file. It will be created in the current editor directory.
- Make desire changes to CSV data.
- Save the CSV data (First time will create the csv file).
- Generate the partition binary (Choose encrypt to encrypt the binary and disable the generate key option to use your own key if desired).
1 change: 1 addition & 0 deletions i18n/en/package.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"espIdf.openDocUrl.title": "ESP-IDF: Open ESP-IDF Documentation...",
"espIdf.clearDocsSearchResult.title": "ESP-IDF: Clear ESP-IDF Search results",
"espIdf.fullClean.title": "ESP-IDF: Full clean project",
"espIdf.webview.nvsPartitionEditor.title": "ESP-IDF: Open NVS Partition Editor",
"debug.initConfig.name": "ESP-IDF: Launch",
"debug.initConfig.description": "A new configuration for launch ESP-IDF projects",
"param.adapterTargetName": "Target name for ESP-IDF Debug Adapter",
Expand Down
1 change: 1 addition & 0 deletions i18n/es/package.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"espIdf.openDocUrl.title": "ESP-IDF: Abrir documentación ESP-IDF...",
"espIdf.clearDocsSearchResult.title": "ESP-IDF: Limpiar resultados de busqueda",
"espIdf.fullClean.title": "ESP-IDF: Limpiar el proyecto",
"espIdf.webview.nvsPartitionEditor.title": "ESP-IDF: Abrir editor de particiones NVS",
"debug.initConfig.name": "Lanzar ESP-IDF:",
"debug.initConfig.description": "Nueva configuración para proyectos ESP-IDF",
"param.adapterTargetName": "Target para el ESP-IDF Debug Adapter",
Expand Down
1 change: 1 addition & 0 deletions i18n/zh-CN/package.i18n.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"espIdf.openDocUrl.title": "ESP-IDF: 打开ESP-IDF文档...",
"espIdf.clearDocsSearchResult.title": "ESP-IDF: 清除ESP-IDF搜索结果",
"espIdf.fullClean.title": "ESP-IDF: 全清洁工程",
"espIdf.webview.nvsPartitionEditor.title": "ESP-IDF: 打开NVS分区编辑器",
"debug.initConfig.name": "ESP-IDF:启动",
"debug.initConfig.description": "启用 ESP-IDF 项目的新配置",
"param.adapterTargetName": "ESP-IDF调试适配器的目标名称",
Expand Down
12 changes: 12 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,7 @@
"onCommand:esp.rainmaker.backend.sync",
"onCommand:espIdf.getEspAdf",
"onCommand:espIdf.getEspMdf",
"onCommand:espIdf.webview.nvsPartitionEditor",
"onView:idfAppTracer",
"onView:idfAppTraceArchive",
"onView:espRainmaker",
Expand Down Expand Up @@ -228,6 +229,11 @@
"command": "menuconfig.start",
"group": "navigation"
},
{
"when": "resourceExtname == .csv",
"group": "navigation",
"command": "espIdf.webview.nvsPartitionEditor"
},
{
"when": "resourceFilename == CMakeLists.txt",
"command": "cmakeListsEditor.start",
Expand Down Expand Up @@ -754,6 +760,10 @@
{
"command": "espIdf.fullClean",
"title": "%espIdf.fullClean.title%"
},
{
"command": "espIdf.webview.nvsPartitionEditor",
"title": "%espIdf.webview.nvsPartitionEditor.title%"
}
],
"breakpoints": [
Expand Down Expand Up @@ -896,6 +906,7 @@
"webpack": "webpack --mode production"
},
"devDependencies": {
"@creativebulma/bulma-tooltip": "^1.2.0",
"@iconify-icons/codicon": "^1.0.6",
"@iconify/vue": "^1.0.6",
"@types/follow-redirects": "^1.8.0",
Expand Down Expand Up @@ -945,6 +956,7 @@
"vue-loader": "^15.7.1",
"vue-property-decorator": "^8.2.1",
"vue-router": "^3.0.2",
"vue-select": "^3.10.8",
"vue-style-loader": "^4.1.2",
"vue-template-compiler": "^2.6.10",
"vuex-class": "^0.3.2",
Expand Down
1 change: 1 addition & 0 deletions package.nls.json
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@
"espIdf.openDocUrl.title": "ESP-IDF: Open ESP-IDF Documentation...",
"espIdf.clearDocsSearchResult.title": "ESP-IDF: Clear ESP-IDF Search results",
"espIdf.fullClean.title": "ESP-IDF: Full clean project",
"espIdf.webview.nvsPartitionEditor.title": "ESP-IDF: Open NVS Partition Editor",
"debug.initConfig.name": "ESP-IDF: Launch",
"debug.initConfig.description": "A new configuration for launch ESP-IDF projects",
"param.adapterTargetName": "Target name for ESP-IDF Debug Adapter",
Expand Down
285 changes: 285 additions & 0 deletions src/espIdf/nvs/partitionTable/panel.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,285 @@
/*
* Project: ESP-IDF VSCode Extension
* File Created: Tuesday, 15th December 2020 4:48:44 pm
* Copyright 2020 Espressif Systems (Shanghai) CO LTD
* 
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
* 
* http://www.apache.org/licenses/LICENSE-2.0
* 
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import { basename, dirname, join } from "path";
import * as vscode from "vscode";
import { constants, readFile, writeFile } from "fs-extra";
import { Logger } from "../../../logger/logger";
import * as idfConf from "../../../idfConfiguration";
import { canAccessFile, execChildProcess } from "../../../utils";
import { OutputChannel } from "../../../logger/outputChannel";

export class NVSPartitionTable {
private static currentPanel: NVSPartitionTable;
private static readonly viewType = "idfNvsPartitionTableEditor";
private static readonly viewTitle = "ESP-IDF NVS Partition Table Editor";

public static async createOrShow(extensionPath: string, filePath: string) {
const column = vscode.window.activeTextEditor
? vscode.window.activeTextEditor.viewColumn
: vscode.ViewColumn.One;

if (!!NVSPartitionTable.currentPanel) {
if (NVSPartitionTable.currentPanel.filePath === filePath) {
return NVSPartitionTable.currentPanel.panel.reveal(column);
}
await NVSPartitionTable.currentPanel.getCSVFromFile(filePath);
return;
} else {
const panel = vscode.window.createWebviewPanel(
NVSPartitionTable.viewType,
NVSPartitionTable.viewTitle,
column,
{
enableScripts: true,
localResourceRoots: [
vscode.Uri.file(join(extensionPath, "dist", "views")),
],
retainContextWhenHidden: true,
}
);
NVSPartitionTable.currentPanel = new NVSPartitionTable(
extensionPath,
filePath,
panel
);
}
}

private static sendMessageToPanel(command: string, payload: object) {
if (this.currentPanel.panel && this.currentPanel.panel.webview) {
this.currentPanel.panel.webview.postMessage({ command, ...payload });
}
}

private extensionPath: string;
private filePath: string;
private readonly panel: vscode.WebviewPanel;
private disposables: vscode.Disposable[] = [];

private constructor(
extensionPath: string,
filePath: string,
panel: vscode.WebviewPanel
) {
this.extensionPath = extensionPath;
this.filePath = filePath;
this.panel = panel;
this.panel.iconPath = vscode.Uri.file(
join(extensionPath, "media", "espressif_icon.png")
);
this.panel.webview.onDidReceiveMessage(
(e) => this.onMessage(e),
null,
this.disposables
);
this.panel.webview.html = this.createEditorHtml(this.panel.webview);
this.panel.onDidDispose(() => this.dispose(), null, this.disposables);
}

private async getCSVFromFile(filePath: string) {
try {
let csvContent: string = "";
if (filePath && filePath.endsWith(".csv")) {
csvContent = await readFile(filePath, "utf-8");
}
if (!csvContent) {
return;
}
this.filePath = filePath;
NVSPartitionTable.sendMessageToPanel("loadInitialData", {
csv: csvContent,
});
} catch (error) {
error.message
? Logger.errorNotify(error.message, error)
: Logger.errorNotify(`Failed to read CSV from ${filePath}`, error);
}
}

private async generateNvsPartition(
encrypt: boolean,
encryptKeyPath: string,
generateKey: boolean,
partitionSize: string
) {
try {
const idfPathDir =
idfConf.readParameter("idf.espIdfPath") || process.env.IDF_PATH;
const pythonBinPath = idfConf.readParameter(
"idf.pythonBinPath"
) as string;
const dirPath = dirname(this.filePath);
const fileName = basename(this.filePath);
const resultName = fileName.replace(".csv", ".bin");
const toolPath = join(
idfPathDir,
"components",
"nvs_flash",
"nvs_partition_generator",
"nvs_partition_gen.py"
);
if (!canAccessFile(pythonBinPath, constants.R_OK)) {
Logger.errorNotify(
"Python binary path is not defined",
new Error("idf.pythonBinPath is not defined")
);
}
if (!canAccessFile(this.filePath, constants.R_OK)) {
Logger.warnNotify(
`${this.filePath} + " is not defined. Save the file first.`
);
}
if (!canAccessFile(toolPath, constants.R_OK)) {
Logger.errorNotify(
"nvs_partition_gen.py is not defined",
new Error(
"nvs_partition_gen.py is not defined, Make sure idf.espIdfPath is correct."
)
);
}
const genEncryptPart = encrypt ? "encrypt" : "generate";
const partToolArgs = [
toolPath,
genEncryptPart,
fileName,
resultName,
partitionSize,
];
if (encrypt) {
const genOrUseKey = generateKey
? "--keygen"
: `--inputkey ${encryptKeyPath}`;
partToolArgs.push(genOrUseKey);
}
OutputChannel.appendLine(`${pythonBinPath} ${partToolArgs.join(" ")}`);
const result = await execChildProcess(
`${pythonBinPath} ${partToolArgs.join(" ")}`,
dirPath
);
OutputChannel.appendLine(result);

if (result.indexOf("Created NVS binary: ===>") !== -1) {
Logger.infoNotify(`Created NVS Binary in ${join(dirPath, resultName)}`);
}
} catch (error) {
const msg = error.message
? error.message
: "Error generating NVS partition";
OutputChannel.appendLine(msg);
Logger.errorNotify(msg, error);
}
}

private dispose() {
NVSPartitionTable.currentPanel = undefined;
this.panel.dispose();
while (this.disposables.length) {
const disposable = this.disposables.pop();
disposable.dispose();
}
}

private createEditorHtml(webview: vscode.Webview): string {
const scriptPath = webview.asWebviewUri(
vscode.Uri.file(
join(this.extensionPath, "dist", "views", "nvsPartitionTable-bundle.js")
)
);
return `<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>ESP-IDF NVS Partition Table Editor</title>
</head>
<body>
<section id="app"></section>
<script src="${scriptPath}"></script>
</body>
</html>`;
}

private async onMessage(message: any) {
switch (message.command) {
case "getInitialData":
await this.getCSVFromFile(this.filePath);
break;
case "genNvsPartition":
if (
typeof message.encrypt !== undefined &&
typeof message.encryptKeyPath !== undefined &&
typeof message.generateKey !== undefined &&
message.partitionSize
) {
await this.generateNvsPartition(
message.encrypt,
message.encryptKeyPath,
message.generateKey,
message.partitionSize
);
}
break;
case "openKeyFile":
const filePath = await this.openFile();
NVSPartitionTable.sendMessageToPanel("openKeyFile", {
keyFilePath: filePath,
});
break;
case "showErrorMessage":
if (message.error) {
Logger.errorNotify(message.error, new Error(message.error));
}
case "saveDataRequest":
if (message.csv) {
this.writeCSVDataToFile(message.csv);
}
break;
default:
break;
}
}

private async openFile() {
const selectedFile = await vscode.window.showOpenDialog({
canSelectFolders: false,
canSelectFiles: true,
canSelectMany: false,
});
if (selectedFile && selectedFile.length > 0) {
return selectedFile[0].fsPath;
} else {
vscode.window.showInformationMessage("No file selected");
}
}

private async writeCSVDataToFile(csv: string) {
try {
if (this.filePath && this.filePath.endsWith(".csv")) {
await writeFile(this.filePath, csv);
Logger.infoNotify(
`NVS Partition table is saved successfully. (${this.filePath})`
);
}
} catch (err) {
return Logger.errorNotify(
`Failed to save the partition data to the file ${this.filePath} due to some error. Error: ${err.message}`,
err
);
}
}
}
Loading

0 comments on commit c90ffde

Please sign in to comment.