diff --git a/.gitignore b/.gitignore index a5d8f72..b889088 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ bin/ dist/ +src/*.jar diff --git a/README.md b/README.md index ae1f080..73ab510 100644 --- a/README.md +++ b/README.md @@ -1,34 +1,90 @@ # Arduino ESP32 filesystem uploader -Arduino plugin which packs sketch data folder into SPIFFS filesystem image, +- Arduino plugin which packs sketch data folder into SPIFFS, LittleFS or FatFS filesystem image, and uploads the image to ESP32 flash memory. +- Added custom **"partition.csv"** file processing if it is located in the sketch folder. +- Added esp32 / esp32s2 chip detection based on Arduino IDE selection. +- Added a choice to "Erase all flash". +- You can have only one of three filesystems on same Arduino project as data partition. + +## Notes for SPIFFS + +- This is the default filesystem implemented in esp-32 core for /data folder +- Go to Arduino IDE menu: ***Tools > Partition Scheme*** and select an entry with SPIFFS partition + +## Notes for LittleFS + +- Same partition scheme as SPIFFS +- Until fully implemented to esp-32 core, it needs an extra library. +It is already considered for next core releases. The mklittlefs tool is provided from there. +- For reference see [LITTLEFS esp32 library](https://github.com/lorol/LITTLEFS) for more details +- If you need the [mklittlefs tool](https://github.com/earlephilhower/mklittlefs) download the [release](https://github.com/earlephilhower/mklittlefs/releases) or find it [archived at previous releases here](https://github.com/lorol/arduino-esp32fs-plugin/releases ) +- Copy **mklittlefs[.exe]** to **/tools** folder of esp32 platform where **espota** and **esptool** (.py or.exe) tools are located + +## Notes for FatFS + +- Go to Arduino IDE menu: ***Tools > Partition Scheme*** and select an entry with FAT partition +- If not provided by the core, you may need additional binary files for Windows or Linux, thanks [@lbernstone](https://github.com/lbernstone) for compiling - or take them from the author [here - mkfatfs tool](https://github.com/labplus-cn/mkfatfs/releases/tag/v1.0) , thanks to [labplus-cn](https://github.com/labplus-cn/mkfatfs) or take from [archived previous release here](https://github.com/lorol/arduino-esp32fs-plugin/releases ) +- If missing, you need to copy **mkfatfs[.exe]** to **/tools** folder of esp32 platform where **espota** and **esptool** (.py or.exe) tools are located +- The usable size of FAT partition is reduced with 1 sector of 4096 bytes (0x1000) to resolve wear leveling space requirement. The image file is flashed with +4096 bytes (0x1000) offset of partition address of csv table entry +- You may need to decrease **maxOpenFiles** at FFat.begin() of your sketch , [see this note](http://marc.merlins.org/perso/arduino/post_2019-03-30_Using-FatFS-FFat-on-ESP32-Flash-With-Arduino.html) +>The FFAT module uses 8KB plus 4KB per concurrent file that can be opened. By default, it allows 10 files to be opened, which means it uses 48KB. IF you want to reduce its memory use, you can tell it to only support one file, and you will save 36KB, leaving you with only 12KB used. +``` +if (!FFat.begin(0, "", 1)) die("Fat FS mount failed. Not enough RAM?"); +``` +- To flash the data folder as FAT partition by **network port (uses espota)**, replace your esp32-core Update library with the [modified files here](https://github.com/lorol/arduino-esp32fatfs-plugin/tree/master/extra/esp32-modified-Update-lib-ffat-espota.zip) ## Installation - Make sure you use one of the supported versions of Arduino IDE and have ESP32 core installed. -- Download the tool archive from [releases page](https://github.com/me-no-dev/arduino-esp32fs-plugin/releases/latest). +- Download **esp32fs.zip** zipped tool from [latest release](https://github.com/lorol/arduino-esp32fs-plugin/releases) - In your Arduino sketchbook directory, create tools directory if it doesn't exist yet. - Unpack the tool into tools directory (the path will look like ```/Arduino/tools/ESP32FS/tool/esp32fs.jar```). +- Make sure you have **mklittlefs[.exe]** and **mkfatfs[.exe]** available at **/tools** folder or in sub-folder there +- For reference, see at [previous releases](https://github.com/lorol/arduino-esp32fs-plugin/releases) for copies of archived binaries in question. +- You can also use provided **package_esp32_index.template.json** to run **get.py** with it and download the missing binary files - Restart Arduino IDE. -On the OS X create the tools directory in ~/Documents/Arduino/ and unpack the files there - ## Usage - Open a sketch (or create a new one and save it). - Go to sketch directory (choose Sketch > Show Sketch Folder). - Create a directory named `data` and any files you want in the file system there. -- Make sure you have selected a board, port, and closed Serial Monitor. -- Select *Tools > ESP32 Sketch Data Upload* menu item. This should start uploading the files into ESP32 flash file system. +- Make sure you have selected a board, port, partition scheme and closed Serial Monitor. +- Select ***Tools > ESP32 Sketch Data Upload*** menu item. +- On drop-down list, select SPIFFS, LittleFS or FatFS you want to make from your /data folder. +- Clicking OK should start uploading the files into ESP32 flash file system. +- Last entry **!Erase Flash!** allows to clean the entire flash if necessary. - When done, IDE status bar will display SPIFFS Image Uploaded message. Might take a few minutes for large file system sizes. + When done, IDE status bar will display the status of Image Uploaded message. Might take a few minutes for large file system sizes. + +## Screenshot + +![Screenshot](tool.png) + +## Quick build on Win: + +- Install Java JDK +- Find the path of javac.exe and jar.exe +- Find where files **arduino-core.jar , commons-codec-1.7.jar , pde.jar** of your Arduino IDE installation are located +- Edit **make_win.bat** to match "your" paths for **set PATH=your\java\bin;%PATH%** and **set ALP=your\arduino\lib** +- Run **make_win.bat** +- Find the **jar** file in src/bin directory ## Credits and license +### The Original Arduino ESP32 filesystem uploader + - Copyright (c) 2015 Hristo Gochkov (hristo at espressif dot com) - Licensed under GPL v2 ([text](LICENSE)) - Maintained by Hristo Gochkov (hristo at espressif dot com) +### Other people, sources and binary files + +- https://github.com/earlephilhower +- https://github.com/labplus-cn/mkfatfs +- https://github.com/lbernstone + ## Issues and suggestions File issues here on github, or ask your questions on the [esp32.com forum](http://esp32.com). diff --git a/make_win.bat b/make_win.bat new file mode 100644 index 0000000..390b3b3 --- /dev/null +++ b/make_win.bat @@ -0,0 +1,12 @@ +cd %~dp0\src +set PATH=your\java\bin;%PATH% +set ALP=your\arduino\lib +IF EXIST bin GOTO NODIR +mkdir bin +:NODIR +del bin\*.jar +rd /S /Q bin\com +javac.exe -target 1.8 -cp ".;%ALP%\arduino-core.jar;%ALP%\commons-codec-1.7.jar;%ALP%\pde.jar" -d bin ESP32FS.java +cd bin +jar.exe cvfM esp32fs.jar * +pause diff --git a/src/ESP32FS.java b/src/ESP32FS.java index eb75aa9..e1bb586 100644 --- a/src/ESP32FS.java +++ b/src/ESP32FS.java @@ -1,380 +1,589 @@ -/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ - -/* - Tool to put the contents of the sketch's "data" subfolder - into an SPIFFS partition image and upload it to an ESP32 MCU - - Copyright (c) 2015 Hristo Gochkov (hristo at espressif dot com) - - This program is free software; you can redistribute it and/or modify - it under the terms of the GNU General Public License as published by - the Free Software Foundation; either version 2 of the License, or - (at your option) any later version. - - This program is distributed in the hope that it will be useful, - but WITHOUT ANY WARRANTY; without even the implied warranty of - MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - GNU General Public License for more details. - - You should have received a copy of the GNU General Public License - along with this program; if not, write to the Free Software Foundation, - Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA -*/ - -package com.esp32.mkspiffs; - -import java.io.File; -import java.io.FileReader; -import java.io.BufferedReader; -import java.io.InputStreamReader; -import java.io.IOException; - -import java.text.SimpleDateFormat; -import java.util.Date; -import java.lang.reflect.Field; -import java.lang.reflect.InvocationTargetException; -import javax.swing.JOptionPane; - -import processing.app.PreferencesData; -import processing.app.Editor; -import processing.app.Base; -import processing.app.BaseNoGui; -import processing.app.Platform; -import processing.app.Sketch; -import processing.app.tools.Tool; -import processing.app.helpers.ProcessUtils; -import processing.app.debug.TargetPlatform; - -import org.apache.commons.codec.digest.DigestUtils; -import processing.app.helpers.FileUtils; - -import cc.arduino.files.DeleteFilesOnShutdown; - -/** - * Example Tools menu entry. - */ -public class ESP32FS implements Tool { - Editor editor; - - - public void init(Editor editor) { - this.editor = editor; - } - - - public String getMenuTitle() { - return "ESP32 Sketch Data Upload"; - } - - private int listenOnProcess(String[] arguments){ - try { - final Process p = ProcessUtils.exec(arguments); - Thread thread = new Thread() { - public void run() { - try { - InputStreamReader reader = new InputStreamReader(p.getInputStream()); - int c; - while ((c = reader.read()) != -1) - System.out.print((char) c); - reader.close(); - - reader = new InputStreamReader(p.getErrorStream()); - while ((c = reader.read()) != -1) - System.err.print((char) c); - reader.close(); - } catch (Exception e){} - } - }; - thread.start(); - int res = p.waitFor(); - thread.join(); - return res; - } catch (Exception e){ - return -1; - } - } - - private void sysExec(final String[] arguments){ - Thread thread = new Thread() { - public void run() { - try { - if(listenOnProcess(arguments) != 0){ - editor.statusError("SPIFFS Upload failed!"); - } else { - editor.statusNotice("SPIFFS Image Uploaded"); - } - } catch (Exception e){ - editor.statusError("SPIFFS Upload failed!"); - } - } - }; - thread.start(); - } - - private String getBuildFolderPath(Sketch s) { - // first of all try the getBuildPath() function introduced with IDE 1.6.12 - // see commit arduino/Arduino#fd1541eb47d589f9b9ea7e558018a8cf49bb6d03 - try { - String buildpath = s.getBuildPath().getAbsolutePath(); - return buildpath; - } - catch (IOException er) { - editor.statusError(er); - } - catch (Exception er) { - try { - File buildFolder = FileUtils.createTempFolder("build", DigestUtils.md5Hex(s.getMainFilePath()) + ".tmp"); - return buildFolder.getAbsolutePath(); - } - catch (IOException e) { - editor.statusError(e); - } - catch (Exception e) { - // Arduino 1.6.5 doesn't have FileUtils.createTempFolder - // String buildPath = BaseNoGui.getBuildFolder().getAbsolutePath(); - java.lang.reflect.Method method; - try { - method = BaseNoGui.class.getMethod("getBuildFolder"); - File f = (File) method.invoke(null); - return f.getAbsolutePath(); - } catch (SecurityException ex) { - editor.statusError(ex); - } catch (IllegalAccessException ex) { - editor.statusError(ex); - } catch (InvocationTargetException ex) { - editor.statusError(ex); - } catch (NoSuchMethodException ex) { - editor.statusError(ex); - } - } - } - return ""; - } - - private long parseInt(String value){ - if(value.startsWith("0x")) return Long.parseLong(value.substring(2), 16); - else return Integer.parseInt(value); - } - - private long getIntPref(String name){ - String data = BaseNoGui.getBoardPreferences().get(name); - if(data == null || data.contentEquals("")) return 0; - return parseInt(data); - } - - private void createAndUpload(){ - long spiStart = 0, spiSize = 0, spiPage = 256, spiBlock = 4096; - String partitions = ""; - - if(!PreferencesData.get("target_platform").contentEquals("esp32")){ - System.err.println(); - editor.statusError("SPIFFS Not Supported on "+PreferencesData.get("target_platform")); - return; - } - - TargetPlatform platform = BaseNoGui.getTargetPlatform(); - - String toolExtension = ".py"; - if(PreferencesData.get("runtime.os").contentEquals("windows")) { - toolExtension = ".exe"; - } else if(PreferencesData.get("runtime.os").contentEquals("macosx")) { - toolExtension = ""; - } - - String pythonCmd; - if(PreferencesData.get("runtime.os").contentEquals("windows")) - pythonCmd = "python.exe"; - else - pythonCmd = "python"; - - String mkspiffsCmd; - if(PreferencesData.get("runtime.os").contentEquals("windows")) - mkspiffsCmd = "mkspiffs.exe"; - else - mkspiffsCmd = "mkspiffs"; - - String espotaCmd = "espota.py"; - if(PreferencesData.get("runtime.os").contentEquals("windows")) - espotaCmd = "espota.exe"; - - Boolean isNetwork = false; - File espota = new File(platform.getFolder()+"/tools"); - File esptool = new File(platform.getFolder()+"/tools"); - String serialPort = PreferencesData.get("serial.port"); - - if(!BaseNoGui.getBoardPreferences().containsKey("build.partitions")){ - System.err.println(); - editor.statusError("Partitions Not Defined for "+BaseNoGui.getBoardPreferences().get("name")); - return; - } - - try { - partitions = BaseNoGui.getBoardPreferences().get("build.partitions"); - if(partitions == null || partitions.contentEquals("")){ - editor.statusError("Partitions Not Found for "+BaseNoGui.getBoardPreferences().get("name")); - return; - } - } catch(Exception e){ - editor.statusError(e); - return; - } - - File partitionsFile = new File(platform.getFolder() + "/tools/partitions", partitions + ".csv"); - if (!partitionsFile.exists() || !partitionsFile.isFile()) { - System.err.println(); - editor.statusError("SPIFFS Error: partitions file " + partitions + ".csv not found!"); - return; - } - - try { - BufferedReader partitionsReader = new BufferedReader(new FileReader(partitionsFile)); - String partitionsLine = ""; - while ((partitionsLine = partitionsReader.readLine()) != null) { - if(partitionsLine.contains("spiffs")) { - partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); - partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); - partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); - while(partitionsLine.startsWith(" ")) partitionsLine = partitionsLine.substring(1); - String pStart = partitionsLine.substring(0, partitionsLine.indexOf(",")); - partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); - while(partitionsLine.startsWith(" ")) partitionsLine = partitionsLine.substring(1); - String pSize = partitionsLine.substring(0, partitionsLine.indexOf(",")); - spiStart = parseInt(pStart); - spiSize = parseInt(pSize); - } - } - if(spiSize == 0){ - System.err.println(); - editor.statusError("SPIFFS Error: partition size could not be found!"); - return; - } - } catch(Exception e){ - editor.statusError(e); - return; - } - - File tool = new File(platform.getFolder() + "/tools", mkspiffsCmd); - if (!tool.exists() || !tool.isFile()) { - tool = new File(platform.getFolder() + "/tools/mkspiffs", mkspiffsCmd); - if (!tool.exists()) { - tool = new File(PreferencesData.get("runtime.tools.mkspiffs.path"), mkspiffsCmd); - if (!tool.exists()) { - System.err.println(); - editor.statusError("SPIFFS Error: mkspiffs not found!"); - return; - } - } - } - - //make sure the serial port or IP is defined - if (serialPort == null || serialPort.isEmpty()) { - System.err.println(); - editor.statusError("SPIFFS Error: serial port not defined!"); - return; - } - - //find espota if IP else find esptool - if(serialPort.split("\\.").length == 4){ - isNetwork = true; - espota = new File(platform.getFolder()+"/tools", espotaCmd); - if(!espota.exists() || !espota.isFile()){ - System.err.println(); - editor.statusError("SPIFFS Error: espota not found!"); - return; - } - } else { - String esptoolCmd = "esptool"+toolExtension; - esptool = new File(platform.getFolder()+"/tools", esptoolCmd); - if(!esptool.exists() || !esptool.isFile()){ - esptool = new File(platform.getFolder()+"/tools/esptool", esptoolCmd); - if(!esptool.exists()){ - esptool = new File(PreferencesData.get("runtime.tools.esptool.path"), esptoolCmd); - if (!esptool.exists()) { - System.err.println(); - editor.statusError("SPIFFS Error: esptool not found!"); - return; - } - } - } - } - - //load a list of all files - int fileCount = 0; - File dataFolder = new File(editor.getSketch().getFolder(), "data"); - if (!dataFolder.exists()) { - dataFolder.mkdirs(); - } - if(dataFolder.exists() && dataFolder.isDirectory()){ - File[] files = dataFolder.listFiles(); - if(files.length > 0){ - for(File file : files){ - if((file.isDirectory() || file.isFile()) && !file.getName().startsWith(".")) fileCount++; - } - } - } - - String dataPath = dataFolder.getAbsolutePath(); - String toolPath = tool.getAbsolutePath(); - String sketchName = editor.getSketch().getName(); - String imagePath = getBuildFolderPath(editor.getSketch()) + "/" + sketchName + ".spiffs.bin"; - String uploadSpeed = BaseNoGui.getBoardPreferences().get("upload.speed"); - - Object[] options = { "Yes", "No" }; - String title = "SPIFFS Create"; - String message = "No files have been found in your data folder!\nAre you sure you want to create an empty SPIFFS image?"; - - if(fileCount == 0 && JOptionPane.showOptionDialog(editor, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]) != JOptionPane.YES_OPTION){ - System.err.println(); - editor.statusError("SPIFFS Warning: mkspiffs canceled!"); - return; - } - - editor.statusNotice("SPIFFS Creating Image..."); - System.out.println("[SPIFFS] data : "+dataPath); - System.out.println("[SPIFFS] start : "+spiStart); - System.out.println("[SPIFFS] size : "+(spiSize/1024)); - System.out.println("[SPIFFS] page : "+spiPage); - System.out.println("[SPIFFS] block : "+spiBlock); - - try { - if(listenOnProcess(new String[]{toolPath, "-c", dataPath, "-p", spiPage+"", "-b", spiBlock+"", "-s", spiSize+"", imagePath}) != 0){ - System.err.println(); - editor.statusError("SPIFFS Create Failed!"); - return; - } - } catch (Exception e){ - editor.statusError(e); - editor.statusError("SPIFFS Create Failed!"); - return; - } - - editor.statusNotice("SPIFFS Uploading Image..."); - System.out.println("[SPIFFS] upload : "+imagePath); - - if(isNetwork){ - System.out.println("[SPIFFS] IP : "+serialPort); - System.out.println(); - if(espota.getAbsolutePath().endsWith(".py")) - sysExec(new String[]{pythonCmd, espota.getAbsolutePath(), "-i", serialPort, "-p", "3232", "-s", "-f", imagePath}); - else - sysExec(new String[]{espota.getAbsolutePath(), "-i", serialPort, "-p", "3232", "-s", "-f", imagePath}); - } else { - String flashMode = BaseNoGui.getBoardPreferences().get("build.flash_mode"); - String flashFreq = BaseNoGui.getBoardPreferences().get("build.flash_freq"); - System.out.println("[SPIFFS] address: "+spiStart); - System.out.println("[SPIFFS] port : "+serialPort); - System.out.println("[SPIFFS] speed : "+uploadSpeed); - System.out.println("[SPIFFS] mode : "+flashMode); - System.out.println("[SPIFFS] freq : "+flashFreq); - System.out.println(); - if(esptool.getAbsolutePath().endsWith(".py")) - sysExec(new String[]{pythonCmd, esptool.getAbsolutePath(), "--chip", "esp32", "--baud", uploadSpeed, "--port", serialPort, "--before", "default_reset", "--after", "hard_reset", "write_flash", "-z", "--flash_mode", flashMode, "--flash_freq", flashFreq, "--flash_size", "detect", ""+spiStart, imagePath}); - else - sysExec(new String[]{esptool.getAbsolutePath(), "--chip", "esp32", "--baud", uploadSpeed, "--port", serialPort, "--before", "default_reset", "--after", "hard_reset", "write_flash", "-z", "--flash_mode", flashMode, "--flash_freq", flashFreq, "--flash_size", "detect", ""+spiStart, imagePath}); - } - } - - public void run() { - createAndUpload(); - } -} +/* -*- mode: java; c-basic-offset: 2; indent-tabs-mode: nil -*- */ + +/* + Tool to put the contents of the sketch's "data" subfolder + into an SPIFFS, LittleFS or FatFS partition image and upload it to an ESP32 MCU + + Copyright (c) 2015 Hristo Gochkov (hristo at espressif dot com) + + This program is free software; you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation; either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program; if not, write to the Free Software Foundation, + Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA +*/ + +package com.esp32.mkspiffs; + +import java.util.*; +import java.io.*; + +import java.text.SimpleDateFormat; +import java.lang.reflect.Field; +import java.lang.reflect.InvocationTargetException; +import javax.swing.JOptionPane; + +import processing.app.PreferencesData; +import processing.app.Editor; +import processing.app.Base; +import processing.app.BaseNoGui; +import processing.app.Platform; +import processing.app.Sketch; +import processing.app.tools.Tool; +import processing.app.helpers.ProcessUtils; +import processing.app.helpers.PreferencesMap; +import processing.app.debug.TargetPlatform; + +import org.apache.commons.codec.digest.DigestUtils; +import processing.app.helpers.FileUtils; + +import cc.arduino.files.DeleteFilesOnShutdown; + +/** + * Taken from https://www.infoworld.com/article/2071275/when-runtime-exec---won-t.html?page=3 + */ +class StreamGobbler extends Thread { + InputStream is; + String type; + + StreamGobbler(InputStream is, String type) { + this.is = is; + this.type = type; + } + + public void run() { + try { + InputStreamReader isr = new InputStreamReader(is); + BufferedReader br = new BufferedReader(isr); + String line=null; + while ( (line = br.readLine()) != null) + System.out.println(type + ">" + line); + } catch (IOException ioe) { + ioe.printStackTrace(); + } + } +} + + +/** + * Example Tools menu entry. + */ +public class ESP32FS implements Tool { + Editor editor; + + + public void init(Editor editor) { + this.editor = editor; + } + + + public String getMenuTitle() { + return "ESP32 Sketch Data Upload"; + } + + private String typefs = ""; + + private int listenOnProcess(String[] arguments){ + try { + Runtime rt = Runtime.getRuntime(); + Process proc = rt.exec(arguments); + // any error message? + StreamGobbler errorGobbler = new StreamGobbler(proc.getErrorStream(), "_"); + + // any output? + StreamGobbler outputGobbler = new StreamGobbler(proc.getInputStream(), "-"); + + // kick them off + errorGobbler.start(); + outputGobbler.start(); + + // any error??? + int exitVal = proc.waitFor(); + + return exitVal; + } catch (Exception e){ + return -1; + } + } + + private void sysExec(final String[] arguments){ + Thread thread = new Thread() { + public void run() { + try { + if(listenOnProcess(arguments) != 0){ + editor.statusError(typefs + " Upload failed!"); + } else { + editor.statusNotice(typefs + " Image Uploaded"); + } + } catch (Exception e){ + editor.statusError(typefs + " Upload failed!"); + } + } + }; + thread.start(); + } + + private String getBuildFolderPath(Sketch s) { + // first of all try the getBuildPath() function introduced with IDE 1.6.12 + // see commit arduino/Arduino#fd1541eb47d589f9b9ea7e558018a8cf49bb6d03 + try { + String buildpath = s.getBuildPath().getAbsolutePath(); + return buildpath; + } + catch (IOException er) { + editor.statusError(er); + } + catch (Exception er) { + try { + File buildFolder = FileUtils.createTempFolder("build", DigestUtils.md5Hex(s.getMainFilePath()) + ".tmp"); + return buildFolder.getAbsolutePath(); + } + catch (IOException e) { + editor.statusError(e); + } + catch (Exception e) { + // Arduino 1.6.5 doesn't have FileUtils.createTempFolder + // String buildPath = BaseNoGui.getBuildFolder().getAbsolutePath(); + java.lang.reflect.Method method; + try { + method = BaseNoGui.class.getMethod("getBuildFolder"); + File f = (File) method.invoke(null); + return f.getAbsolutePath(); + } catch (SecurityException ex) { + editor.statusError(ex); + } catch (IllegalAccessException ex) { + editor.statusError(ex); + } catch (InvocationTargetException ex) { + editor.statusError(ex); + } catch (NoSuchMethodException ex) { + editor.statusError(ex); + } + } + } + return ""; + } + + private long parseInt(String value){ + if(value.endsWith("m") || value.endsWith("M")) return 1024*1024*Long.decode(value.substring(0, (value.length() - 1))); + else if(value.endsWith("k") || value.endsWith("K")) return 1024*Long.decode(value.substring(0, (value.length() - 1))); + else return Long.decode(value); + } + + private long getIntPref(String name){ + String data = BaseNoGui.getBoardPreferences().get(name); + if(data == null || data.contentEquals("")) return 0; + return parseInt(data); + } + + private void createAndUpload(){ + long spiStart = 0, spiSize = 0, spiPage = 256, spiBlock = 4096, spiOffset = 0; + String partitions = ""; + + if (typefs == "FatFS") spiOffset = 4096; + + System.out.println("Chip : " + getChip()); + + if(!PreferencesData.get("target_platform").contains("esp32")){ + System.err.println(); + editor.statusError(typefs + " Not Supported on "+PreferencesData.get("target_platform")); + return; + } + + TargetPlatform platform = BaseNoGui.getTargetPlatform(); + + String toolExtension = ".py"; + if(PreferencesData.get("runtime.os").contentEquals("windows")) { + toolExtension = ".exe"; + } else if(PreferencesData.get("runtime.os").contentEquals("macosx")) { + toolExtension = ""; + } + + String pythonCmd; + if(PreferencesData.get("runtime.os").contentEquals("windows")) + pythonCmd = "python.exe"; + else + pythonCmd = "python"; + + String mkspiffsCmd; + if(PreferencesData.get("runtime.os").contentEquals("windows")) + if (typefs == "LittleFS") mkspiffsCmd = "mklittlefs.exe"; + else if (typefs == "FatFS") mkspiffsCmd = "mkfatfs.exe"; + else mkspiffsCmd = "mkspiffs.exe"; + else + if (typefs == "LittleFS") mkspiffsCmd = "mklittlefs"; + else if (typefs == "FatFS") mkspiffsCmd = "mkfatfs"; + else mkspiffsCmd = "mkspiffs"; + + String espotaCmd = "espota.py"; + if(PreferencesData.get("runtime.os").contentEquals("windows")) + espotaCmd = "espota.exe"; + + Boolean isNetwork = false; + File espota = new File(platform.getFolder()+"/tools"); + File esptool = new File(platform.getFolder()+"/tools"); + String serialPort = PreferencesData.get("serial.port"); + + if(!BaseNoGui.getBoardPreferences().containsKey("build.partitions")){ + System.err.println(); + editor.statusError("Partitions Not Defined for "+BaseNoGui.getBoardPreferences().get("name")); + return; + } + + File partitionsFile = new File(editor.getSketch().getFolder(), "partitions.csv"); + + if (partitionsFile.exists() && partitionsFile.isFile()) { + System.out.println("Using partitions.csv from sketch folder."); + + } else { + System.out.println("Using partition scheme from Arduino IDE."); + try { + partitions = BaseNoGui.getBoardPreferences().get("build.partitions"); + if(partitions == null || partitions.contentEquals("")){ + editor.statusError("Partitions Not Found for "+BaseNoGui.getBoardPreferences().get("name")); + return; + } + } catch(Exception e){ + editor.statusError(e); + return; + } + + partitionsFile = new File(platform.getFolder() + "/tools/partitions", partitions + ".csv"); + + if (!partitionsFile.exists() || !partitionsFile.isFile()) { + System.err.println(); + editor.statusError(typefs + " Error: partitions file " + partitions + ".csv not found!"); + return; + } + } + + try { + BufferedReader partitionsReader = new BufferedReader(new FileReader(partitionsFile)); + String partitionsLine = ""; + long spiPrevEnd = 0; + boolean isDataLine = false; + while ((partitionsLine = partitionsReader.readLine()) != null) { + if (!partitionsLine.substring(0, 1).equals("#")) { + if( ((typefs != "FatFS") && partitionsLine.contains("spiffs")) || ((typefs == "FatFS") && partitionsLine.contains("ffat"))){ + isDataLine = true; + } + partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); + partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); + partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); + while(partitionsLine.startsWith(" ")) partitionsLine = partitionsLine.substring(1); + String pStart = partitionsLine.substring(0, partitionsLine.indexOf(",")); + partitionsLine = partitionsLine.substring(partitionsLine.indexOf(",")+1); + while(partitionsLine.startsWith(" ")) partitionsLine = partitionsLine.substring(1); + String pSize = partitionsLine.substring(0, partitionsLine.indexOf(",")); + + //System.out.println("From CSV, Partition Start: " + pStart + ", Size: " + pSize); + + if (isDataLine) { + if (pStart == null || pStart.trim().isEmpty()) { + spiStart = spiPrevEnd + spiOffset; + } else { + spiStart = parseInt(pStart) + spiOffset; + } + spiSize = parseInt(pSize) - spiOffset; + break; + } else { + if (pSize != null && !pSize.trim().isEmpty()) { + if (pStart == null || pStart.trim().isEmpty()) { + spiPrevEnd += parseInt(pSize); + } else { + spiPrevEnd = parseInt(pStart) + parseInt(pSize); + } + } + spiSize = 0; + } + } + } + if(spiSize == 0){ + System.err.println(); + editor.statusError(typefs + " Error: partition size could not be found!"); + return; + } + } catch(Exception e){ + editor.statusError(e); + return; + } + + System.out.println("Start: 0x" + String.format("%x", spiStart)); + System.out.println("Size : 0x" + String.format("%x", spiSize)); + + File tool = new File(platform.getFolder() + "/tools", mkspiffsCmd); + if (!tool.exists() || !tool.isFile()) { + tool = new File(platform.getFolder() + "/tools/mk" + typefs.toLowerCase(), mkspiffsCmd); + if (!tool.exists()) { + tool = new File(PreferencesData.get("runtime.tools.mk" + typefs.toLowerCase() + ".path"), mkspiffsCmd); + if (!tool.exists()) { + System.err.println(); + editor.statusError(typefs + " Error: mk" + typefs.toLowerCase() + "not found!"); + return; + } + } + } + System.out.println("mk" + typefs.toLowerCase() + " : " + tool.getAbsolutePath()); + System.out.println(); + + //make sure the serial port or IP is defined + if (serialPort == null || serialPort.isEmpty()) { + System.err.println(); + editor.statusError(typefs + " Error: serial port not defined!"); + return; + } + + //find espota if IP else find esptool + if(serialPort.split("\\.").length == 4){ + isNetwork = true; + espota = new File(platform.getFolder()+"/tools", espotaCmd); + if(!espota.exists() || !espota.isFile()){ + espota = new File(platform.getFolder()+"/tools", "espota.py"); //fall-back to .py + if(!espota.exists() || !espota.isFile()){ + System.err.println(); + editor.statusError(typefs + " Error: espota not found!"); + return; + } + } + System.out.println("espota : "+espota.getAbsolutePath()); + System.out.println(); + } else { + String esptoolCmd = "esptool"+toolExtension; + esptool = new File(platform.getFolder()+"/tools", esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(platform.getFolder()+"/tools/esptool_py", esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(platform.getFolder()+"/tools/esptool", esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(PreferencesData.get("runtime.tools.esptool_py.path"), esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(PreferencesData.get("runtime.tools.esptool.path"), esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + System.err.println(); + editor.statusError("Error: esptool not found!"); + return; + } + } + } + } + } + System.out.println("esptool : "+esptool.getAbsolutePath()); + System.out.println(); + } + + //load a list of all files + int fileCount = 0; + File dataFolder = new File(editor.getSketch().getFolder(), "data"); + if (!dataFolder.exists()) { + dataFolder.mkdirs(); + } + if(dataFolder.exists() && dataFolder.isDirectory()){ + File[] files = dataFolder.listFiles(); + if(files.length > 0){ + for(File file : files){ + if((file.isDirectory() || file.isFile()) && !file.getName().startsWith(".")) fileCount++; + } + } + } + + String dataPath = dataFolder.getAbsolutePath(); + String toolPath = tool.getAbsolutePath(); + String sketchName = editor.getSketch().getName(); + String imagePath = getBuildFolderPath(editor.getSketch()) + "/" + sketchName + "." + typefs.toLowerCase() + ".bin"; + String uploadSpeed = BaseNoGui.getBoardPreferences().get("upload.speed"); + + Object[] options = { "Yes", "No" }; + String title = typefs + " Create"; + String message = "No files have been found in your data folder!\nAre you sure you want to create an empty " + typefs + " image?"; + + if(fileCount == 0 && JOptionPane.showOptionDialog(editor, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]) != JOptionPane.YES_OPTION){ + System.err.println(); + editor.statusError(typefs + " Warning: mktool canceled!"); + return; + } + + editor.statusNotice(typefs + " Creating Image..."); + System.out.println("[" + typefs + "] data : "+dataPath); + System.out.println("[" + typefs + "] offset : "+spiOffset); + System.out.println("[" + typefs + "] start : "+spiStart); + System.out.println("[" + typefs + "] size : "+(spiSize/1024)); + if (typefs != "FatFS") { + System.out.println("[" + typefs + "] page : "+spiPage); + System.out.println("[" + typefs + "] block : "+spiBlock); + } + + try { + if (typefs == "FatFS") { + if(listenOnProcess(new String[]{toolPath, "-c", dataPath, "-s", spiSize+"", imagePath}) != 0){ + System.err.println(); + editor.statusError(typefs + " Create Failed!"); + return; + } + } else { + if(listenOnProcess(new String[]{toolPath, "-c", dataPath, "-p", spiPage+"", "-b", spiBlock+"", "-s", spiSize+"", imagePath}) != 0){ + System.err.println(); + editor.statusError(typefs + " Create Failed!"); + return; + } + } + + } catch (Exception e){ + editor.statusError(e); + editor.statusError(typefs + " Create Failed!"); + return; + } + + editor.statusNotice(typefs + " Uploading Image..."); + System.out.println("[" + typefs + "] upload : "+imagePath); + + if(isNetwork){ + System.out.println("[" + typefs + "] IP : "+serialPort); + System.out.println("Running: " + espota.getAbsolutePath() + " -i " + serialPort + " -p 3232 -s -f " + imagePath); + System.out.println(); + if(espota.getAbsolutePath().endsWith(".py")) + sysExec(new String[]{pythonCmd, espota.getAbsolutePath(), "-i", serialPort, "-p", "3232", "-s", "-f", imagePath}); // other flags , "-d", "-r", "-t", "50" + else + sysExec(new String[]{espota.getAbsolutePath(), "-i", serialPort, "-p", "3232", "-s", "-f", imagePath}); + } else { + String flashMode = BaseNoGui.getBoardPreferences().get("build.flash_mode"); + String flashFreq = BaseNoGui.getBoardPreferences().get("build.flash_freq"); + System.out.println("[" + typefs + "] address: "+spiStart); + System.out.println("[" + typefs + "] port : "+serialPort); + System.out.println("[" + typefs + "] speed : "+uploadSpeed); + System.out.println("[" + typefs + "] mode : "+flashMode); + System.out.println("[" + typefs + "] freq : "+flashFreq); + System.out.println(); + // change after "write_flash" "-z" to "-u" (--no_compress) below to build file for esp32fs_no_compress.zip + if(esptool.getAbsolutePath().endsWith(".py")) + sysExec(new String[]{pythonCmd, esptool.getAbsolutePath(), "--chip", getChip(), "--baud", uploadSpeed, "--port", serialPort, "--before", "default_reset", "--after", "hard_reset", "write_flash", "-z", "--flash_mode", flashMode, "--flash_freq", flashFreq, "--flash_size", "detect", ""+spiStart, imagePath}); + else + sysExec(new String[]{esptool.getAbsolutePath(), "--chip", getChip(), "--baud", uploadSpeed, "--port", serialPort, "--before", "default_reset", "--after", "hard_reset", "write_flash", "-z", "--flash_mode", flashMode, "--flash_freq", flashFreq, "--flash_size", "detect", ""+spiStart, imagePath}); + } + } + + + private void eraseFlash(){ + System.out.println("Chip : " + getChip()); + if(!PreferencesData.get("target_platform").contains("esp32")){ + System.err.println(); + editor.statusError(typefs + " Not Supported on "+PreferencesData.get("target_platform")); + return; + } + + TargetPlatform platform = BaseNoGui.getTargetPlatform(); + + String toolExtension = ".py"; + if(PreferencesData.get("runtime.os").contentEquals("windows")) { + toolExtension = ".exe"; + } else if(PreferencesData.get("runtime.os").contentEquals("macosx")) { + toolExtension = ""; + } + + String pythonCmd; + if(PreferencesData.get("runtime.os").contentEquals("windows")) + pythonCmd = "python.exe"; + else + pythonCmd = "python"; + + Boolean isNetwork = false; + + File esptool = new File(platform.getFolder()+"/tools"); + String serialPort = PreferencesData.get("serial.port"); + + //make sure the serial port or IP is defined + if (serialPort == null || serialPort.isEmpty()) { + System.err.println(); + editor.statusError(typefs + " Error: serial port not defined!"); + return; + } + + //find port + if(serialPort.split("\\.").length == 4){ + isNetwork = true; + } else { + String esptoolCmd = "esptool"+toolExtension; + esptool = new File(platform.getFolder()+"/tools", esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(platform.getFolder()+"/tools/esptool_py", esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(platform.getFolder()+"/tools/esptool", esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(PreferencesData.get("runtime.tools.esptool_py.path"), esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + esptool = new File(PreferencesData.get("runtime.tools.esptool.path"), esptoolCmd); + if(!esptool.exists() || !esptool.isFile()){ + System.err.println(); + editor.statusError("Error: esptool not found!"); + return; + } + } + } + } + } + System.out.println("esptool : "+esptool.getAbsolutePath()); + System.out.println(); + } + + Object[] options = { "Yes", "No" }; + String title = "Erase All Flash"; + String message = "Are you sure?"; + + if(JOptionPane.showOptionDialog(editor, message, title, JOptionPane.YES_NO_OPTION, JOptionPane.QUESTION_MESSAGE, null, options, options[1]) != JOptionPane.YES_OPTION){ + System.err.println(); + editor.statusError("Warning: Erase All Flash canceled!"); + return; + } + + editor.statusNotice("Erasing all Flash started..."); + System.out.println("Erasing all Flash started..."); + + if(isNetwork){ + System.out.println("Cannot be done through OTA, IP : "+serialPort); + System.out.println(); + } else { + System.out.println("Port: "+serialPort); + System.out.println(); + if(esptool.getAbsolutePath().endsWith(".py")) + sysExec(new String[]{pythonCmd, esptool.getAbsolutePath(), "--chip", getChip(), "--port", serialPort, "--before", "default_reset", "--after", "hard_reset", "erase_flash"}); + else + sysExec(new String[]{esptool.getAbsolutePath(), "--chip", getChip(), "--port", serialPort, "--before", "default_reset", "--after", "hard_reset", "erase_flash"}); + } + } + + private String getChip(){ + PreferencesMap prefs = BaseNoGui.getTargetBoard().getPreferences(); + String mcu = prefs.get("build.mcu", "esp32"); + return mcu; + } + + public void run() { + String sketchName = editor.getSketch().getName(); + Object[] options = { "LittleFS", "SPIFFS", "FatFS", "!Erase Flash!" }; + typefs = (String)JOptionPane.showInputDialog(editor, + "Select FS for " + sketchName + + " /data folder", + "Filesystem", + JOptionPane.PLAIN_MESSAGE, + null, + options, + "LittleFS"); + if ((typefs != null) && (typefs.length() > 0)) { + if (typefs == "!Erase Flash!") { + eraseFlash(); + } else { + createAndUpload(); + } + } else { + System.out.println("Tool Exit."); + return; + } + + } +} diff --git a/tool.png b/tool.png new file mode 100644 index 0000000..0b2efe2 Binary files /dev/null and b/tool.png differ