From bcfeef8004c5724db2efadb72720738d3c7810f8 Mon Sep 17 00:00:00 2001 From: rjwats Date: Sun, 29 Dec 2019 17:54:12 +0000 Subject: [PATCH] Interface data storage in PROGMEM (#71) Adds a webpack plugin to package interface as PROGMEM into a header file in the framework. Adds a build flag to optionally enable serving from PROGMEM or SPIFFS as required Adds documentation changes to describe changes --- .gitignore | 3 +- .travis.yml | 6 +- README.md | 92 +++++++------ interface/config-overrides.js | 6 +- interface/package-lock.json | 20 ++- interface/package.json | 6 +- interface/progmem-generator.js | 122 ++++++++++++++++++ interface/public/css/roboto.css | 6 +- interface/public/fonts/{ro-li.w2 => li.woff2} | Bin interface/public/fonts/{ro-me.w2 => me.woff2} | Bin interface/public/fonts/{ro-re.w2 => re.woff2} | Bin lib/framework/APSettingsService.h | 4 +- lib/framework/ESP8266React.cpp | 27 +++- lib/framework/ESP8266React.h | 7 +- lib/framework/SecuritySettingsService.cpp | 3 + lib/framework/SecuritySettingsService.h | 3 + platformio.ini | 55 ++++---- scripts/build_interface.py | 36 ++++++ timelib_fix.py => scripts/timelib_fix.py | 9 +- src/main.cpp | 4 + 20 files changed, 324 insertions(+), 85 deletions(-) create mode 100644 interface/progmem-generator.js rename interface/public/fonts/{ro-li.w2 => li.woff2} (100%) rename interface/public/fonts/{ro-me.w2 => me.woff2} (100%) rename interface/public/fonts/{ro-re.w2 => re.woff2} (100%) create mode 100644 scripts/build_interface.py rename timelib_fix.py => scripts/timelib_fix.py (77%) diff --git a/.gitignore b/.gitignore index 3a9d311..facc712 100644 --- a/.gitignore +++ b/.gitignore @@ -1,10 +1,9 @@ .pio -.pioenvs -.piolibdeps .clang_complete .gcc-flags.json *Thumbs.db /data/www +/lib/framework/WWWData.h /interface/build /interface/node_modules .vscode diff --git a/.travis.yml b/.travis.yml index a16a93a..4668306 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,6 +1,10 @@ language: python python: - - "2.7" + - "3.8" + +before_install: + - nvm install 10.15.3 + - nvm use 10.15.3 sudo: false cache: diff --git a/README.md b/README.md index ee10d02..890afcf 100644 --- a/README.md +++ b/README.md @@ -30,7 +30,6 @@ You will need the following before you can get started. * [PlatformIO](https://platformio.org/) - IDE for development * [Node.js](https://nodejs.org) - For building the interface with npm -* Bash shell, or [Git Bash](https://gitforwindows.org/) if you are under windows ### Building and uploading the firmware @@ -74,35 +73,15 @@ Alternatively run the 'upload' target: platformio run -t upload ``` -### Building the interface +### Building & uploading the interface The interface has been configured with create-react-app and react-app-rewired so the build can customized for the target device. The large artefacts are gzipped and source maps and service worker are excluded from the production build. This reduces the production build to around ~200k, which easily fits on the device. -Change to the ['interface'](interface) directory with your bash shell (or Git Bash) and use the standard commands you would with any react app built with create-react-app: - -#### Change to interface directory - -```bash -cd interface -``` - -#### Download and install the node modules - -```bash -npm install -``` - -#### Build the interface - -```bash -npm run build -``` - -> **Note**: The build command will also delete the previously built interface, in the ['data/www'](data/www) directory, replacing it with the freshly built one ready to upload to the device. +The interface will be automatically built by PlatformIO before it builds the firmware. The project can be configured to serve the interface from either SPIFFS or PROGMEM as your project requires. The default configuration is to serve the content from SPIFFS which requires an additional upload step which is documented below. #### Uploading the file system image -The compiled user interface may be uploaded to the device by pressing the "Upload File System image" button: +If service content from SPIFFS (default), build the project first. Then the compiled interface may be uploaded to the device by pressing the "Upload File System image" button: ![uploadfs](/media/uploadfs.png?raw=true "uploadfs") @@ -112,15 +91,43 @@ Alternatively run the 'uploadfs' target: platformio run -t uploadfs ``` +#### Serving the interface from PROGMEM + +You can configure the project to serve the interface from PROGMEM by uncommenting the -D PROGMEM_WWW build flag in ['platformio.ini'](platformio.ini) then re-building and uploading the firmware to the device. + +Be aware that this will consume ~150k of program space which can be especially problematic if you already have a large build artefact or if you have added large javascript dependencies to the interface. The ESP32 binaries are large already, so this will be a problem if you are using one of these devices and require this type of setup. + +A method for working around this issue can be to reduce the amount of space allocated to SPIFFS by configuring the device to use a differnt strategy partitioning. If you don't require SPIFFS other than for storing config one approach might be to configure a minimal SPIFFS partition. + +For a ESP32 (4mb variant) there is a handy "min_spiffs.csv" partition table which can be enabled easily: + +```yaml +[env:node32s] +board_build.partitions = min_spiffs.csv +platform = espressif32 +board = node32s +``` + +This is largley left as an exersise for the reader as everyone's requirements will vary. + ### Running the interface locally -You can run a local development server to allow you preview changes to the front end without the need to upload a file system image to the device after each change. Change to the interface directory and run the following command: +You can run a local development server to allow you preview changes to the front end without the need to upload a file system image to the device after each change. + +Change to the ['interface'](interface) directory with your bash shell (or Git Bash) and use the standard commands you would with any react app built with create-react-app: ```bash +cd interface +``` + +Install the npm dependencies, if required and start the development server: + +```bash +npm install npm start ``` -> **Note**: To run the interface locally you will need to modify the endpoint root path and enable CORS. +> **Note**: To run the interface locally you may need to modify the endpoint root path and enable CORS. #### Changing the endpoint root @@ -141,7 +148,9 @@ You can enable CORS on the back end by uncommenting the -D ENABLE_CORS build fla ## Device Configuration -As well as containing the interface, the SPIFFS image (in the ['data'](data) folder) contains a JSON settings file for each of the configurable features. The config files can be found in the ['data/config'](data/config) directory: +The SPIFFS image (in the ['data'](data) folder) contains a JSON settings file for each of the configurable features. + +The config files can be found in the ['data/config'](data/config) directory: File | Description ---- | ----------- @@ -173,30 +182,31 @@ It is recommended that you change the JWT secret and user credentials from their This project supports ESP8266 and ESP32 platforms. To support OTA programming, enough free space to upload the new sketch and file system image will be required. It is recommended that a board with at least 2mb of flash is used. -By default, the target device is "esp12e". This is a common ESP8266 variant with 4mb of flash: +The pre-configured environments are "esp12e" and "node32s". These are common ESP8266/ESP32 variants with 4mb of flash: -![ESP12E](/media/esp12e.jpg?raw=true "ESP12E") +![ESP12E](/media/esp12e.jpg?raw=true "ESP12E") ![ESP32](/media/esp32.jpg?raw=true "ESP32") -The settings file ['platformio.ini'](platformio.ini) configures the platform and board: +The settings file ['platformio.ini'](platformio.ini) configures the supported environments. Modify these, or add new environments for the devides you need to support. The default environments are as follows: -``` +```yaml [env:esp12e] platform = espressif8266 board = esp12e -``` +board_build.f_cpu = 160000000L -If you want to build for an ESP32 device, all you need to do is re-configure ['platformio.ini'](platformio.ini) with your devices settings. - -![ESP32](/media/esp32.jpg?raw=true "ESP32") - -Building for the common esp32 "node32s" board for example requires the following configuration: - -``` [env:node32s] platform = espressif32 board = node32s ``` +If you want to build for a different device, all you need to do is re-configure ['platformio.ini'](platformio.ini) and select an alternative environment by modifying the default_envs variable. Building for the common esp32 "node32s" board for example: + +```yaml +[platformio] +;default_envs = esp12e +default_envs = node32s +``` + ## Customizing and theming The framework, and MaterialUI allows for a reasonable degree of customization with little effort. @@ -274,7 +284,11 @@ void setup() { Serial.begin(SERIAL_BAUD_RATE); // start the file system (must be done before starting the framework) +#ifdef ESP32 + SPIFFS.begin(true); +#elif defined(ESP8266) SPIFFS.begin(); +#endif // start the framework and demo project esp8266React.begin(); diff --git a/interface/config-overrides.js b/interface/config-overrides.js index 80a6221..a80d407 100644 --- a/interface/config-overrides.js +++ b/interface/config-overrides.js @@ -1,7 +1,8 @@ const ManifestPlugin = require('webpack-manifest-plugin'); const WorkboxWebpackPlugin = require('workbox-webpack-plugin'); const MiniCssExtractPlugin = require('mini-css-extract-plugin'); -const CompressionPlugin = require("compression-webpack-plugin"); +const CompressionPlugin = require('compression-webpack-plugin'); +const ProgmemGenerator = require('./progmem-generator.js'); const path = require('path'); const fs = require('fs'); @@ -21,6 +22,9 @@ module.exports = function override(config, env) { miniCssExtractPlugin.options.filename = "css/[id].[contenthash:4].css"; miniCssExtractPlugin.options.chunkFilename = "css/[id].[contenthash:4].c.css"; + // build progmem data files + config.plugins.push(new ProgmemGenerator({ outputPath: "../lib/framework/WWWData.h", bytesPerLine: 20 })); + // add compression plugin, compress javascript config.plugins.push(new CompressionPlugin({ filename: "[path].gz[query]", diff --git a/interface/package-lock.json b/interface/package-lock.json index f5b38b4..7228359 100644 --- a/interface/package-lock.json +++ b/interface/package-lock.json @@ -8598,11 +8598,18 @@ "integrity": "sha512-jYdeOMPy9vnxEqFRRo6ZvTZ8d9oPb+k18PKoYNYUe2stVEBPPwsln/qWzdbmaIvnhZ9v2P+CuecK+fpUfsV2mA==" }, "mime-types": { - "version": "2.1.24", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.24.tgz", - "integrity": "sha512-WaFHS3MCl5fapm3oLxU4eYDw77IQM2ACcxQ9RIxfaC3ooc6PFuBMGZZsYpvoXS5D5QTWPieo1jjLdAm3TBP3cQ==", + "version": "2.1.25", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.25.tgz", + "integrity": "sha512-5KhStqB5xpTAeGqKBAMgwaYMnQik7teQN4IAzC7npDv6kzeU6prfkR67bc87J1kWMPGkoaZSq1npmexMgkmEVg==", "requires": { - "mime-db": "1.40.0" + "mime-db": "1.42.0" + }, + "dependencies": { + "mime-db": { + "version": "1.42.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.42.0.tgz", + "integrity": "sha512-UbfJCR4UAVRNgMpfImz05smAXK7+c+ZntjaA26ANtkXLlOe947Aag5zdIcKQULAiF9Cq4WxBi9jUs5zkA84bYQ==" + } } }, "mimic-fn": { @@ -13483,6 +13490,11 @@ "camelcase": "^5.0.0", "decamelize": "^1.2.0" } + }, + "zlib": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/zlib/-/zlib-1.0.5.tgz", + "integrity": "sha1-bnyXL8NxxkWmr7A6sUdp3vEU/MA=" } } } diff --git a/interface/package.json b/interface/package.json index e91090c..68bdbf0 100644 --- a/interface/package.json +++ b/interface/package.json @@ -7,6 +7,7 @@ "@material-ui/icons": "^4.5.1", "compression-webpack-plugin": "^2.0.0", "jwt-decode": "^2.2.0", + "mime-types": "^2.1.25", "moment": "^2.24.0", "notistack": "^0.9.6", "prop-types": "^15.7.2", @@ -17,11 +18,12 @@ "react-material-ui-form-validator": "^2.0.9", "react-router": "^5.1.1", "react-router-dom": "^5.1.1", - "react-scripts": "3.0.1" + "react-scripts": "3.0.1", + "zlib": "^1.0.5" }, "scripts": { "start": "react-app-rewired start", - "build": "react-app-rewired build && rm -rf ../data/www && cp -r build ../data/www", + "build": "react-app-rewired build", "test": "react-app-rewired test --env=jsdom", "eject": "react-scripts eject" }, diff --git a/interface/progmem-generator.js b/interface/progmem-generator.js new file mode 100644 index 0000000..674adae --- /dev/null +++ b/interface/progmem-generator.js @@ -0,0 +1,122 @@ +const { resolve, relative, sep } = require('path'); +const { readdirSync, existsSync, unlinkSync, readFileSync, createWriteStream } = require('fs'); +var zlib = require('zlib'); +var mime = require('mime-types'); + +const ARDUINO_INCLUDES = "#include \n\n"; + +function getFilesSync(dir, files = []) { + readdirSync(dir, { withFileTypes: true }).forEach(entry => { + const entryPath = resolve(dir, entry.name); + if (entry.isDirectory()) { + getFilesSync(entryPath, files); + } else { + files.push(entryPath); + } + }) + return files; +} + +function coherseToBuffer(input) { + return Buffer.isBuffer(input) ? input : Buffer.from(input); +} + +function cleanAndOpen(path) { + if (existsSync(path)) { + unlinkSync(path); + } + return createWriteStream(path, { flags: "w+" }); +} + +class ProgmemGenerator { + + constructor(options = {}) { + const { outputPath, bytesPerLine = 20, indent = " ", includes = ARDUINO_INCLUDES } = options; + this.options = { outputPath, bytesPerLine, indent, includes }; + } + + apply(compiler) { + compiler.hooks.emit.tapAsync( + { name: 'ProgmemGenerator' }, + (compilation, callback) => { + const { outputPath, bytesPerLine, indent, includes } = this.options; + const fileInfo = []; + const writeStream = cleanAndOpen(resolve(compilation.options.context, outputPath)); + try { + const writeIncludes = () => { + writeStream.write(includes); + } + + const writeFile = (relativeFilePath, buffer) => { + const variable = "ESP_REACT_DATA_" + fileInfo.length; + const mimeType = mime.lookup(relativeFilePath); + var size = 0; + writeStream.write("const uint8_t " + variable + "[] PROGMEM = {"); + const zipBuffer = zlib.gzipSync(buffer); + zipBuffer.forEach((b) => { + if (!(size % bytesPerLine)) { + writeStream.write("\n"); + writeStream.write(indent); + } + writeStream.write("0x" + ("00" + b.toString(16).toUpperCase()).substr(-2) + ","); + size++; + }); + if (size % bytesPerLine) { + writeStream.write("\n"); + } + writeStream.write("};\n\n"); + fileInfo.push({ + uri: '/' + relativeFilePath.replace(sep, '/'), + mimeType, + variable, + size + }); + }; + + const writeFiles = () => { + // process static files + const buildPath = compilation.options.output.path; + for (const filePath of getFilesSync(buildPath)) { + const readStream = readFileSync(filePath); + const relativeFilePath = relative(buildPath, filePath); + writeFile(relativeFilePath, readStream); + } + // process assets + const { assets } = compilation; + Object.keys(assets).forEach((relativeFilePath) => { + writeFile(relativeFilePath, coherseToBuffer(assets[relativeFilePath].source())); + }); + } + + const generateWWWClass = () => { + return `typedef std::function RouteRegistrationHandler; + +class WWWData { +${indent}public: +${indent.repeat(2)}static void registerRoutes(RouteRegistrationHandler handler) { +${fileInfo.map(file => `${indent.repeat(3)}handler("${file.uri}", "${file.mimeType}", ${file.variable}, ${file.size});`).join('\n')} +${indent.repeat(2)}} +}; +`; + } + + const writeWWWClass = () => { + writeStream.write(generateWWWClass()); + } + + writeIncludes(); + writeFiles(); + writeWWWClass(); + + writeStream.on('finish', () => { + callback(); + }); + } finally { + writeStream.end(); + } + } + ); + } +} + +module.exports = ProgmemGenerator; diff --git a/interface/public/css/roboto.css b/interface/public/css/roboto.css index 89c6414..ac21f0f 100644 --- a/interface/public/css/roboto.css +++ b/interface/public/css/roboto.css @@ -3,20 +3,20 @@ font-family: 'Roboto'; font-style: normal; font-weight: 300; - src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/ro-li.w2) format('woff2'); + src: local('Roboto Light'), local('Roboto-Light'), url(../fonts/li.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; } @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 400; - src: local('Roboto'), local('Roboto-Regular'), url(../fonts/ro-re.w2) format('woff2'); + src: local('Roboto'), local('Roboto-Regular'), url(../fonts/re.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; } @font-face { font-family: 'Roboto'; font-style: normal; font-weight: 500; - src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/ro-me.w2) format('woff2'); + src: local('Roboto Medium'), local('Roboto-Medium'), url(../fonts/me.woff2) format('woff2'); unicode-range: U+0000-00FF, U+0131, U+0152-0153, U+02BB-02BC, U+02C6, U+02DA, U+02DC, U+2000-206F, U+2074, U+20AC, U+2122, U+2212, U+2215; } \ No newline at end of file diff --git a/interface/public/fonts/ro-li.w2 b/interface/public/fonts/li.woff2 similarity index 100% rename from interface/public/fonts/ro-li.w2 rename to interface/public/fonts/li.woff2 diff --git a/interface/public/fonts/ro-me.w2 b/interface/public/fonts/me.woff2 similarity index 100% rename from interface/public/fonts/ro-me.w2 rename to interface/public/fonts/me.woff2 diff --git a/interface/public/fonts/ro-re.w2 b/interface/public/fonts/re.woff2 similarity index 100% rename from interface/public/fonts/ro-re.w2 rename to interface/public/fonts/re.woff2 diff --git a/lib/framework/APSettingsService.h b/lib/framework/APSettingsService.h index b7a2831..1a95638 100644 --- a/lib/framework/APSettingsService.h +++ b/lib/framework/APSettingsService.h @@ -13,8 +13,8 @@ #define DNS_PORT 53 -#define AP_DEFAULT_SSID "ssid" -#define AP_DEFAULT_PASSWORD "password" +#define AP_DEFAULT_SSID "ESP8266-React" +#define AP_DEFAULT_PASSWORD "esp-react" #define AP_SETTINGS_FILE "/config/apSettings.json" #define AP_SETTINGS_SERVICE_PATH "/rest/apSettings" diff --git a/lib/framework/ESP8266React.cpp b/lib/framework/ESP8266React.cpp index 134c3d1..bb3f8b6 100644 --- a/lib/framework/ESP8266React.cpp +++ b/lib/framework/ESP8266React.cpp @@ -13,13 +13,37 @@ ESP8266React::ESP8266React(AsyncWebServer* server, FS* fs) : _ntpStatus(server, &_securitySettingsService), _apStatus(server, &_securitySettingsService), _systemStatus(server, &_securitySettingsService) { +#ifdef PROGMEM_WWW + // Serve static resources from PROGMEM + WWWData::registerRoutes( + [server, this](const String& uri, const String& contentType, const uint8_t* content, size_t len) { + ArRequestHandlerFunction requestHandler = [contentType, content, len](AsyncWebServerRequest* request) { + AsyncWebServerResponse* response = request->beginResponse_P(200, contentType, content, len); + response->addHeader("Content-Encoding", "gzip"); + request->send(response); + }; + server->on(uri.c_str(), HTTP_GET, requestHandler); + // Serving non matching get requests with "/index.html" + // OPTIONS get a straight up 200 response + if (uri.equals("/index.html")) { + server->onNotFound([requestHandler](AsyncWebServerRequest* request) { + if (request->method() == HTTP_GET) { + requestHandler(request); + } else if (request->method() == HTTP_OPTIONS) { + request->send(200); + } else { + request->send(404); + } + }); + } + }); +#else // Serve static resources from /www/ server->serveStatic("/js/", SPIFFS, "/www/js/"); server->serveStatic("/css/", SPIFFS, "/www/css/"); server->serveStatic("/fonts/", SPIFFS, "/www/fonts/"); server->serveStatic("/app/", SPIFFS, "/www/app/"); server->serveStatic("/favicon.ico", SPIFFS, "/www/favicon.ico"); - // Serving all other get requests with "/www/index.htm" // OPTIONS get a straight up 200 response server->onNotFound([](AsyncWebServerRequest* request) { @@ -31,6 +55,7 @@ ESP8266React::ESP8266React(AsyncWebServer* server, FS* fs) : request->send(404); } }); +#endif // Disable CORS if required #if defined(ENABLE_CORS) diff --git a/lib/framework/ESP8266React.h b/lib/framework/ESP8266React.h index d76ac14..c6331bf 100644 --- a/lib/framework/ESP8266React.h +++ b/lib/framework/ESP8266React.h @@ -4,9 +4,9 @@ #include #ifdef ESP32 -#include #include #include +#include #elif defined(ESP8266) #include #include @@ -26,6 +26,10 @@ #include #include +#ifdef PROGMEM_WWW +#include +#endif + class ESP8266React { public: ESP8266React(AsyncWebServer* server, FS* fs); @@ -52,6 +56,7 @@ class ESP8266React { NTPStatus _ntpStatus; APStatus _apStatus; SystemStatus _systemStatus; + }; #endif diff --git a/lib/framework/SecuritySettingsService.cpp b/lib/framework/SecuritySettingsService.cpp index c19160c..47bc549 100644 --- a/lib/framework/SecuritySettingsService.cpp +++ b/lib/framework/SecuritySettingsService.cpp @@ -17,6 +17,9 @@ void SecuritySettingsService::readFromJsonObject(JsonObject& root) { for (JsonVariant user : root["users"].as()) { _users.push_back(User(user["username"], user["password"], user["admin"])); } + } else { + _users.push_back(User(DEFAULT_ADMIN_USERNAME, DEFAULT_ADMIN_USERNAME, true)); + _users.push_back(User(DEFAULT_GUEST_USERNAME, DEFAULT_GUEST_USERNAME, false)); } } diff --git a/lib/framework/SecuritySettingsService.h b/lib/framework/SecuritySettingsService.h index 7818434..9ff0257 100644 --- a/lib/framework/SecuritySettingsService.h +++ b/lib/framework/SecuritySettingsService.h @@ -4,6 +4,9 @@ #include #include +#define DEFAULT_ADMIN_USERNAME "admin" +#define DEFAULT_GUEST_USERNAME "guest" + #define SECURITY_SETTINGS_FILE "/config/securitySettings.json" #define SECURITY_SETTINGS_PATH "/rest/securitySettings" diff --git a/platformio.ini b/platformio.ini index c06a090..bf652f2 100644 --- a/platformio.ini +++ b/platformio.ini @@ -1,35 +1,40 @@ -; PlatformIO Project Configuration File -; -; Build options: build flags, source filter -; Upload options: custom upload port, speed and extra flags -; Library options: dependencies, extra library storages -; Advanced options: extra scripting -; -; Please visit documentation for the other options and examples -; http://docs.platformio.org/page/projectconf.html -[env:esp12e] -platform = espressif8266 -board = esp12e -board_build.f_cpu = 160000000L - -extra_scripts = pre:timelib_fix.py - -framework = arduino -monitor_speed = 115200 - -; Uncomment & modify the lines below in order to configure OTA updates -;upload_flags = -; --port=8266 -; --auth=esp-react -;upload_port = 192.168.0.11 +[platformio] +default_envs = esp12e +;default_envs = node32s +[env] build_flags= -D NO_GLOBAL_ARDUINOOTA ; Uncomment ENABLE_CORS to enable Cross-Origin Resource Sharing (required for local React development) ;-D ENABLE_CORS -D CORS_ORIGIN=\"http://localhost:3000\" + ; Uncomment PROGMEM_WWW to enable the storage of the WWW data in PROGMEM + ;-D PROGMEM_WWW + +; Uncomment & modify the lines below in order to configure OTA updates +;upload_flags = +; --port=8266 +; --auth=esp-react +;upload_port = 192.168.0.11 + +framework = arduino +monitor_speed = 115200 + +extra_scripts = + pre:scripts/timelib_fix.py + pre:scripts/build_interface.py + lib_deps = NtpClientLib@>=2.5.1,<3.0.0 ArduinoJson@>=6.0.0,<7.0.0 ESP Async WebServer@>=1.2.0,<2.0.0 - AsyncTCP@>=1.0.3,<2.0.0 + +[env:esp12e] +platform = espressif8266 +board = esp12e +board_build.f_cpu = 160000000L + +[env:node32s] +;board_build.partitions = min_spiffs.csv +platform = espressif32 +board = node32s diff --git a/scripts/build_interface.py b/scripts/build_interface.py new file mode 100644 index 0000000..0c927cf --- /dev/null +++ b/scripts/build_interface.py @@ -0,0 +1,36 @@ +from pathlib import Path +from shutil import copytree +from shutil import rmtree +from subprocess import check_output, Popen, PIPE, STDOUT, CalledProcessError +from os import chdir + +Import("env") + +def flagExists(flag): + buildFlags = env.ParseFlags(env["BUILD_FLAGS"]) + for define in buildFlags.get("CPPDEFINES"): + if (define == flag or (isinstance(define, list) and define[0] == flag)): + return True + +def buildWeb(): + chdir("interface") + print("Building interface with npm") + try: + env.Execute("npm install") + env.Execute("npm run build") + buildPath = Path("build") + wwwPath = Path("../data/www") + if wwwPath.exists() and wwwPath.is_dir(): + rmtree(wwwPath) + if not flagExists("PROGMEM_WWW"): + print("Copying interface to data directory") + copytree(buildPath, wwwPath) + finally: + chdir("..") + +if (len(BUILD_TARGETS) == 0 or "upload" in BUILD_TARGETS): + buildWeb() +else: + print("Skipping build interface step for target(s): " + ", ".join(BUILD_TARGETS)) + + diff --git a/timelib_fix.py b/scripts/timelib_fix.py similarity index 77% rename from timelib_fix.py rename to scripts/timelib_fix.py index 6e9a22c..35957ad 100644 --- a/timelib_fix.py +++ b/scripts/timelib_fix.py @@ -1,6 +1,7 @@ import os import sys import re + Import("env") # Find files under 'root' of a given 'fileName' in directories matching 'subDirectoryPattern' @@ -26,14 +27,14 @@ def deleteTimeHeader(libDepsDir): if numDeletionCandidates == 1: os.remove(deletionCandidates[0]) elif numDeletionCandidates > 1: - os.write(2, 'Can\'t delete Time.h, more than one instance found:\n' + '\n'.join(deletionCandidates)) + os.write(2, "Can\'t delete Time.h, more than one instance found:\n" + "\n".join(deletionCandidates)) sys.exit(1) # old lib deps directory -deleteTimeHeader(os.path.join(env.subst('$PROJECT_DIR'), '.piolibdeps')) +deleteTimeHeader(os.path.join(env.subst("$PROJECT_DIR"), ".piolibdeps")) # pre 4.x lib deps directory -deleteTimeHeader(os.path.join(env.subst('$PROJECTLIBDEPS_DIR'), env.subst('$PIOENV'))) +deleteTimeHeader(os.path.join(env.subst("$PROJECTLIBDEPS_DIR"), env.subst("$PIOENV"))) # >4.x lib deps directory -deleteTimeHeader(os.path.join(env.subst('$PROJECT_LIBDEPS_DIR'), env.subst('$PIOENV'))) +deleteTimeHeader(os.path.join(env.subst("$PROJECT_LIBDEPS_DIR"), env.subst("$PIOENV"))) diff --git a/src/main.cpp b/src/main.cpp index 752348c..4e1254f 100644 --- a/src/main.cpp +++ b/src/main.cpp @@ -13,7 +13,11 @@ void setup() { Serial.begin(SERIAL_BAUD_RATE); // start the file system (must be done before starting the framework) +#ifdef ESP32 + SPIFFS.begin(true); +#elif defined(ESP8266) SPIFFS.begin(); +#endif // start the framework and demo project esp8266React.begin();