From 32d76bddadc77b273a6d9bcfb31ec98b43a58c47 Mon Sep 17 00:00:00 2001 From: Luc <8822552+luc-github@users.noreply.github.com> Date: Tue, 2 Jun 2026 19:18:28 +0800 Subject: [PATCH 1/4] Make tab accessibles anytime Split configuration.json by tab for easier maintenance --- API.md | 206 +- README.md | 2 + build/index.html | 2 +- package.json | 3 +- src/components/TabBar/index.js | 73 +- src/configuration.json | 2371 ----------------------- src/configuration/index.js | 21 + src/configuration/tabs/defaults.js | 3 + src/configuration/tabs/defaults.json | 41 + src/configuration/tabs/devices.js | 3 + src/configuration/tabs/devices.json | 575 ++++++ src/configuration/tabs/features.js | 3 + src/configuration/tabs/features.json | 486 +++++ src/configuration/tabs/filesystems.js | 3 + src/configuration/tabs/filesystems.json | 466 +++++ src/configuration/tabs/network.js | 3 + src/configuration/tabs/network.json | 751 +++++++ src/configuration/tabs/others.js | 3 + src/configuration/tabs/others.json | 139 ++ src/configuration/tabs/security.js | 3 + src/configuration/tabs/security.json | 41 + src/configuration/tabs/update.js | 3 + src/configuration/tabs/update.json | 76 + src/contexts/DatasContext.js | 2 +- src/pages/config/index.js | 92 +- src/pages/config/steps.js | 88 + src/style/components/_navbar.scss | 2 +- src/tabs/step/index.js | 342 ++-- tools/validate_configuration.js | 126 ++ 29 files changed, 3144 insertions(+), 2785 deletions(-) delete mode 100644 src/configuration.json create mode 100644 src/configuration/index.js create mode 100644 src/configuration/tabs/defaults.js create mode 100644 src/configuration/tabs/defaults.json create mode 100644 src/configuration/tabs/devices.js create mode 100644 src/configuration/tabs/devices.json create mode 100644 src/configuration/tabs/features.js create mode 100644 src/configuration/tabs/features.json create mode 100644 src/configuration/tabs/filesystems.js create mode 100644 src/configuration/tabs/filesystems.json create mode 100644 src/configuration/tabs/network.js create mode 100644 src/configuration/tabs/network.json create mode 100644 src/configuration/tabs/others.js create mode 100644 src/configuration/tabs/others.json create mode 100644 src/configuration/tabs/security.js create mode 100644 src/configuration/tabs/security.json create mode 100644 src/configuration/tabs/update.js create mode 100644 src/configuration/tabs/update.json create mode 100644 src/pages/config/steps.js create mode 100644 tools/validate_configuration.js diff --git a/API.md b/API.md index 2f540d1..288d1f1 100644 --- a/API.md +++ b/API.md @@ -1,52 +1,154 @@ -""" -This file, `configuration.json`, contains the configuration settings for the application. It is used to store various parameters and options that control the behavior of the application. - -""" -# API - -The configuration file is `configuration.json` and it is a JSON file, it contains the configuration settings for the application. It is used to store various parameters and options that control the behavior of the application. It is located in src directory with the following fields: -1 - the tabs: the tabs that are displayed in the application -which are the following: - - the features tab, the default tab that is displayed when the application is launched, describes the features of the esp board - - the network tab, the tab that contains the network capabilities of the esp board - - the filesystem tab, the tab that contains the filesystem capabilities of the esp board - - the update tab, the tab that contains the update capabilities of the esp board - - the devices tab, the tab that contains the connected devices capabilities of the esp board - - the security tab, the tab that contains the security settings available - - the others tab, the tab that contains the other settings available that are not included in the previous tabs - -2 - the tab is a dictionary that contains the following fields: - - id: the id of the control, it is also used for the dependency between the controls - - description: the description of the control, it is used to display the control in the application - - setting: if the control is a setting or not, (true or false), if false it will be set as comment - - type: the type of the control, it can be one of the following: - - text: a text input - - select: a select input - - boolean: a boolean input - - group: a group of controls - - value: the content of the control, that a list of controls or groups of controls - -3 - the control is a dictionary that contains the following fields: - - id: the id of the control, it is also used for the dependency between the controls - - description: the description of the control, it is used to display the control in the application - - label: the label of the control, it is used to display the control in the application - - setting: if the control is a setting or not, (true or false), if false it will be set as comment - - define: if the control is a definition then set the label of the define - - type: the type of the control, it can be one of the following: - - text: a text input - - select: a select input - - boolean: a boolean input - - group: a group of controls - - depends: the id of the control that the current control depends on and the list of values of the control that the current control depends on (value), or the list of values of the control that the current control do not depends on (notvalue), if value is a text define then need to add also the `\"`` of begining and end of string - - - value: the content of the control, that a list of controls or groups of controls, if values is -1 it use default value - - options: if type is select, then the value is a list of options, defined by the following fields: - - label: the description of the option - - value: the value of the option - - depend: the id of the control that the current control depends on and value of the control that the current control depends on - - help: the help of the option - - isport: if the control is a port or not, (true or false), if true it will be set as port, and use the available port list accross all process, no need to set the possible options - - ispin: if the control is a pin or not, (true or false), if true it will be set as pin, and use the available pin list accross all process, no need to set the possible options - - usedescforoptions: if the description of the control is used for the options or not, (true or false), if true it will be used for the options, if false it will not be used for the options, so no need to repeat the description in the options - - disableiffalse: disable the control for the configuration if the value of the control is false - +# ESP3D-Configurator — Configuration schema + +This document describes how the configurator defines UI tabs, form fields, dependencies, and generated firmware files. + +Configuration is split per tab under `src/configuration/` (one JSON file per wizard section). + +## Architecture + +``` +src/ + configuration/ + index.js # Aggregates all tab sections into one object + tabs/ + features.json # Tab data (array of groups) + features.js # import ... from "./features.json" + network.json + ... + pages/config/ + steps.js # Tab bar order, routes, icons (UI registry) + contexts/ + DatasContext.js # Loads aggregated configuration + tabs/ + step/ # Renders step tabs; pins.json / ports.json + generate/ # Builds configuration.h and platformio.ini +``` + +| Layer | Role | +|-------|------| +| `configTabs` (`steps.js`) | Wizard navigation: label, route, link id, icon, `section` key | +| `tabs/
.json` | Field definitions for one configurator step | +| `configuration/index.js` | `{ features: [...], network: [...], ... }` for the app | +| `GenerateTab` | Reads full configuration and exports `configuration.h` / `platformio.ini` | + +The `default` section exists in `configuration/index.js` (from `defaults.json`) and is included in generated headers, but it has **no dedicated tab** in the UI. + +## Configurator tabs (UI) + +Defined in `src/pages/config/steps.js` (`configTabs`): + +| Tab | Route | Config section (`current`) | +|-----|-------|----------------------------| +| Features | `/config/features` | `features` | +| Network | `/config/network` | `network` | +| Filesystems | `/config/filesystems` | `filesystems` | +| Update | `/config/update` | `update` | +| Devices | `/config/devices` | `devices` | +| Security | `/config/security` | `security` | +| Others | `/config/others` | `others` | +| Download | `/config/generate` | (generation only) | + +To add a new wizard tab: + +1. Create `src/configuration/tabs/
.json` (array of groups). +2. Create `src/configuration/tabs/
.js` that default-exports that JSON. +3. Register the section in `src/configuration/index.js`. +4. Add an entry to `configTabs` in `src/pages/config/steps.js` (order = wizard order). +5. Routes are built automatically in `src/pages/config/index.js` from `configTabs`. + +## Tab file format + +Each `tabs/
.json` file is a **JSON array** of top-level **groups**: + +```json +[ + { + "id": "mygroup", + "description": "Group help text", + "label": "Group title", + "setting": true, + "type": "group", + "depend": { "id": "otherField", "value": [true] }, + "value": [ /* controls */ ] + } +] +``` + +## Control (field) format + +Each item inside `group.value` is a control: + +| Field | Description | +|-------|-------------| +| `id` | Unique identifier (used by `depend` and `getValueId()`). **Must be unique across the whole configuration.** | +| `label` | Short title in the UI | +| `description` | Longer help text | +| `type` | `text`, `number`, `select`, `boolean`, or `group` (nested) | +| `value` | Default value (`boolean`, string, or `-1` for “none” on selects) | +| `setting` | If `true`, emitted as `#define` in `configuration.h`; if `false`, comment only | +| `define` | C macro name (e.g. `WIFI_FEATURE`) | +| `depend` | Visibility rule (see below) | +| `options` | For `select`: list of `{ label, value, help?, depend? }` | +| `disableiffalse` | For booleans: omit define when value is false | +| `needquote` | Wrap string values in quotes in generated header | +| `hide` | Skip in generated output | +| `header` | Extra lines prepended before the define | +| `ispin` | Use shared pin list (`src/tabs/step/pins.json`) | +| `isport` | Use shared serial port list (`src/tabs/step/ports.json`) | +| `usedefault` | For pin/port selects: show “Default” instead of “None” | +| `usedescforoptions` | Repeat `description` as comment for select defines | + +### Dependencies (`depend`) + +Controls and options can be shown only when other fields match: + +```json +"depend": { "id": "wifi", "value": [true] } +``` + +```json +"depend": { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } +``` + +Multiple conditions (all must match): + +```json +"depend": [ + { "id": "ethernet", "value": [true] }, + { "id": "ethernetboard", "notvalue": "TYPE_ETH_PHY_W5500" } +] +``` + +For string defines in `depend.value`, use the same quoting as in `value` (e.g. `"\"script\""`). + +**Note:** Some legacy entries use the string `"true"` instead of boolean `true` for ethernet-related fields. Prefer real booleans for new fields. + +### Pin and port allocation + +When `ispin` or `isport` is set, the step UI tracks used pins/ports globally so the same resource is not selected twice. Lists come from: + +- `src/tabs/step/pins.json` +- `src/tabs/step/ports.json` + +Per-field `options` can extend or override list entries (e.g. board-specific pins). + +## Generated outputs + +On the **Download** tab: + +- **configuration.h** — `#define` lines from all sections where `setting` is true and visibility/`depend` rules pass (`src/tabs/generate/index.js`). +- **platformio.ini** — Environment derived from MCU, flash size, WiFi/Ethernet/BT, camera, display, etc. + +Settings in the `default` section are included in `configuration.h` even without a UI tab. + +## Maintenance notes + +- Avoid duplicate `id` values (e.g. multiple `ssdpmodelename` for different MCUs). Duplicates break `getValueId()` and can cause React key warnings; use distinct ids or a single control with `depend` on options. +- After editing JSON, run `npm run validate:config` (duplicate ids, broken `depend`) then `npm run build` or `npm run dev`. + +## Quick checklist: new feature flag + +1. Add the control under the right `tabs/
.json` group (or new group). +2. Set `define`, `type`, `value`, `setting`, and `depend` if needed. +3. Rebuild and test the tab in the UI and on Download (preview + download). +4. Confirm the matching define exists in [ESP3D](https://github.com/luc-github/ESP3D) firmware. diff --git a/README.md b/README.md index c27cf08..3708dc2 100644 --- a/README.md +++ b/README.md @@ -5,6 +5,8 @@ Project of Web Configurator to generate ESP3D configuration file. +See [API.md](API.md) for the configuration schema (tabs, fields, dependencies, and how to add features). + Using github-action / issues form bring too many constraints with not really great user experience, so I went for a standalone html file instead, and it is hosted in github static page area It use same element as ESP3D-WebUI to save development time (yeah I am super lazy...): diff --git a/build/index.html b/build/index.html index e184020..f63189a 100644 --- a/build/index.html +++ b/build/index.html @@ -1 +1 @@ -ESP3D-Configurator \ No newline at end of file +ESP3D-Configurator \ No newline at end of file diff --git a/package.json b/package.json index 482e687..1ca0d17 100644 --- a/package.json +++ b/package.json @@ -8,7 +8,8 @@ "front": "webpack serve --config config/webpack.dev.js ", "server": "nodemon config/server.js", "dev": "concurrently \"npm run server\" \"npm run front\"", - "build": "webpack --config config/webpack.prod.js" + "build": "webpack --config config/webpack.prod.js", + "validate:config": "node tools/validate_configuration.js" }, "author": "", "license": "ISC", diff --git a/src/components/TabBar/index.js b/src/components/TabBar/index.js index c5b44fd..144ebb3 100644 --- a/src/components/TabBar/index.js +++ b/src/components/TabBar/index.js @@ -21,89 +21,24 @@ TabBar.js - ESP3D WebUI Tabs bar file import { h } from "preact" import { Link } from "../Router" import { T } from "../Translations" -import { AppLogo } from "../Images/logo" -import { useUiContext, useUiContextFn } from "../../contexts" -import { - Camera, - Download, - HardDrive, - Tool, - Wifi, - Lock, - Upload, -} from "preact-feather" +import { configTabs } from "../../pages/config/steps" /* * Local const * */ -const defaultLinks = [ - { - label: "Features", - icon: , - href: "/config/features", - id: "featuresLink", - }, - { - label: "Network", - icon: , - href: "/config/network", - id: "networkLink", - }, - { - label: "Filesystems", - icon: , - href: "/config/filesystems", - id: "filesystemsLink", - }, - { - label: "Update", - icon: , - href: "/config/update", - id: "updateLink", - }, - { - label: "Devices", - icon: , - href: "/config/devices", - id: "devicesLink", - }, - - { - label: "Security", - icon: , - href: "/config/security", - id: "securityLink", - }, - { - label: "Others", - icon: , - href: "/config/others", - id: "othersLink", - }, - { - label: "Download", - icon: , - href: "/config/generate", - id: "generateLink", - }, -] const TabBar = () => { - const { uisettings } = useUiContext() return (
    - {defaultLinks && - defaultLinks.map(({ label, icon, href, id }) => { + {configTabs && + configTabs.map(({ label, icon, route, id }) => { return (
  • { - //TBD - }} + href={route} > {icon} diff --git a/src/configuration.json b/src/configuration.json deleted file mode 100644 index 9a836b4..0000000 --- a/src/configuration.json +++ /dev/null @@ -1,2371 +0,0 @@ -{ - "features": [ - { - "id": "mcu", - "description": "ESP Board using ESP3D firmware", - "label": "Board description", - "setting": false, - "type": "group", - "value": [ - { - "id": "targetmcu", - "define": "ESP_MCU", - "description": "MCU", - "label": "MCU", - "type": "select", - "value": "esp32", - "setting": false, - "options": [ - { - "label": "ESP8285", - "value": "esp8285" - }, - { - "label": "ESP8266", - "value": "esp8266" - }, - { - "label": "ESP32", - "value": "esp32" - }, - { - "label": "ESP32-S2", - "value": "esp32s2" - }, - { - "label": "ESP32-S3", - "value": "esp32s3" - }, - { - "label": "ESP32-C3", - "value": "esp32c3" - }, - { - "label": "ESP32-C6", - "value": "esp32c6" - } - ] - }, - { - "id": "targetflashsize", - "define": "FLASH_SIZE", - "description": "ESP flash size", - "label": "ESP flash size", - "type": "select", - "value": "4", - "setting": false, - "options": [ - { - "label": "1MB", - "value": "1", - "depend": { - "id": "targetmcu", - "value": ["esp8266", "esp8285"] - }, - "help": "No OTA/ Web update possible" - }, - { - "label": "2MB", - "value": "2", - "help": "No OTA/ Web update possible" - }, - { - "label": "4MB", - "value": "4" - }, - { - "label": "8MB", - "value": "8" - }, - { - "label": "16MB", - "value": "16" - } - ] - }, - { - "id": "formfactor", - "define": "ESP_FORM_FACTOR", - "description": "Pins form factor", - "label": "Form factor", - "type": "select", - "value": "original", - "setting": false, - "options": [ - { - "label": "Original (all pins)", - "value": "original" - }, - { - "label": "ESP01 (8 pins)", - "value": "esp01" - } - ] - }, - - { - "id": "flash_mod1", - "define": "flash_mode", - "description": "Flash mode", - "label": "Flash mode", - "type": "select", - "value": "dio", - "depend": { - "id": "targetmcu", - "value": ["esp8266", "esp8285"] - }, - "setting": false, - "options": [ - { - "label": "qio", - "value": "qio" - }, - { - "label": "dio", - "value": "dio" - }, - { - "label": "dout", - "value": "dout" - }, - { - "label": "qout", - "value": "qout" - } - ] - }, - { - "id": "flash_mode", - "define": "flash_mode", - "description": "Flash mode", - "label": "Flash mode", - "type": "select", - "value": "qio", - "depend": { - "id": "targetmcu", - "notvalue": ["esp8266", "esp8285"] - }, - "setting": false, - "options": [ - { - "label": "qio", - "value": "qio" - }, - { - "label": "dio", - "value": "dio" - }, - { - "label": "dout", - "value": "dout" - }, - { - "label": "qout", - "value": "qout" - } - ] - }, - { - "id": "resetmethod", - "define": "upload_resetmethod", - "description": "Reset method for upload", - "label": "Reset method for upload", - "type": "select", - "depend": { - "id": "targetmcu", - "value": ["esp8266", "esp8285"] - }, - "value": "ck", - "setting": false, - "options": [ - { - "label": "ck", - "value": "ck", - "help": "Need to manually set GPIO 0 to gnd" - }, - { - "label": "nodemcu", - "value": "nodemcu", - "help": "Automatically set GPIO 0 to gnd via 2 NPN transistors" - }, - { - "label": "wifio", - "value": "wifio", - "help": "Automatically set GPIO 0 to gnd via TX and DTR controls" - } - ] - }, - { - "id": "has_psram", - "define": "BOARD_HAS_PSRAM", - "description": "Has External PSRAM", - "label": "Has PSRAM", - "type": "boolean", - "depend": { - "id": "targetmcu", - "notvalue": ["esp8266", "esp8285"] - }, - "value": false, - "setting": false - }, - { - "id": "memory_type", - "define": "board_build.arduino.memory_type", - "description": "Memory type", - "label": "Memory type", - "type": "select", - "value": "qio_qspi", - "depend": [ - { - "id": "targetmcu", - "value": ["esp32s3"] - }, - { - "id": "has_psram", - "value": [true] - } - ], - "setting": false, - "options": [ - { - "label": "Dio Flash / Octal PSRAM", - "value": "dio_opi" - }, - { - "label": "Quad", - "value": "qio_qspi" - }, - { - "label": "Quad Flash/ Octal PSRAM", - "value": "qio_opi" - }, - { - "label": "Octal", - "value": "opi_opi" - } - ] - } - ] - }, - { - "id": "communication", - "description": "Settings and protocols", - "label": "Serial Communications", - "setting": true, - "type": "group", - "value": [ - { - "id": "serialprotocol", - "define": "COMMUNICATION_PROTOCOL", - "description": "Communication protocol used by ESP3D to discuss with printer or TFT", - "label": "Serial Communication protocol", - "type": "select", - "value": "RAW_SERIAL", - "setting": true, - "options": [ - { - "label": "Raw serial", - "value": "RAW_SERIAL", - "help": "Basic serial protocol, without data change" - }, - { - "label": "MKS serial", - "value": "MKS_SERIAL", - "help": "This is a MakerBase communication protocol, used with MKS printers and TFT, it encapsulated data in a custom protocol" - } - ] - }, - { - "id": "serialport", - "define": "ESP_SERIAL_OUTPUT", - "description": "Which serial port ESP3D use to communicate with printer or TFT", - "label": "Serial port", - "type": "select", - "value": "USE_SERIAL_0", - "setting": true, - "isport": true - }, - { - "id": "serialbuffer", - "define": "SERIAL_RX_BUFFER_SIZE", - "description": "Maximum size of the serial buffer", - "label": "Serial buffer size", - "type": "select", - "value": "512", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "0 Byte", - "value": "0" - }, - { - "label": "128 Bytes", - "value": "128" - }, - { - "label": "256 Bytes", - "value": "256" - }, - { - "label": "512 Bytes", - "value": "512" - }, - { - "label": "1024 Bytes", - "value": "1024" - } - ] - }, - { - "id": "usbserial", - "define": "USB_SERIAL_FEATURE", - "label": "Use USB Serial", - "description": "Enable USB serial OTG port", - "type": "boolean", - "depend": { - "id": "targetmcu", - "value": ["esp32s3", "esp32s2"] - }, - "value": false, - "disableiffalse": true, - "setting": true - }, - { - "id": "serialBridge", - "define": "SERIAL_BRIDGE_FEATURE", - "label": "Use serial bridge", - "description": "Enable serial bridge feature", - "type": "boolean", - "depend": { - "id": "targetmcu", - "notvalue": ["esp8266", "esp8285"] - }, - "value": false, - "disableiffalse": true, - "hide": true, - "setting": false - }, - { - "id": "serialBridgeport", - "define": "ESP_SERIAL_BRIDGE_OUTPUT", - "description": "Which serial port serial bridge to output to", - "depend": { - "id": "serialBridge", - "value": [true] - }, - "label": "Serial bridge port", - "type": "select", - "value": "-1", - "setting": true, - "isport": true - } - ] - }, - { - "id": "targetFW", - "description": "Targeted firmware that ESP3D will communicate with", - "label": "Target firmware", - "setting": true, - "type": "group", - "value": [ - { - "id": "systemtype", - "define": "SYSTEM_TYPE", - "description": "The type of your system", - "label": "System type", - "type": "select", - "value": "Printer3D", - "setting": false, - "options": [ - { - "label": "CNC", - "value": "CNC", - "help": "CNC system used for milling/laser engraving/cutting" - }, - { - "label": "3D printer", - "value": "Printer3D", - "help": "3D printer system used with filaments" - }, - { - "label": "Sand Table", - "value": "SandTable", - "help": "System doing kinetic art, zen garden, sand table, etc." - } - ] - }, - { - "id": "defaultfw", - "define": "DEFAULT_FW", - "description": "The firmware that ESP3D will use by default", - "label": "Target firmware", - "type": "select", - "value": "UNKNOWN_FW", - "setting": true, - "options": [ - { - "label": "Undefined", - "value": "UNKNOWN_FW", - "help": "Undefined firmware" - }, - { - "label": "Marlin", - "value": "MARLIN", - "help": "Marlin 2.x firmware", - "depend": { - "id": "systemtype", - "value": ["Printer3D"] - } - }, - { - "label": "Repetier", - "value": "REPETIER", - "help": "Repetier 1.x/2.x firmware", - "depend": { - "id": "systemtype", - "value": ["Printer3D"] - } - }, - { - "label": "Smoothieware", - "value": "SMOOTHIEWARE", - "help": "Smoothieware 1.x/2.x firmware", - "depend": { - "id": "systemtype", - "value": ["Printer3D"] - } - }, - { - "label": "Grbl", - "value": "GRBL", - "help": "Grbl 1.1 firmware", - "depend": { - "id": "systemtype", - "value": ["CNC", "SandTable"] - } - } - ] - } - ] - } - ], - "network": [ - { - "id": "radiomode", - "description": "The radio mode ESP3D communicate with the network", - "label": "Radio mode of ESP3D", - "setting": true, - "type": "group", - "value": [ - { - "id": "wifi", - "define": "WIFI_FEATURE", - "label": "Use WiFi ", - "description": "Enable wifi communications", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "ethernet", - "define": "ETH_FEATURE", - "label": "Use Ethernet ", - "description": "Enable ethernet communications", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true, - "depend": [ - { "id": "formfactor", "notvalue": ["esp01"] }, - { - "id": "targetmcu", - "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] - } - ] - }, - { - "id": "ethernetboard", - "define": "ESP3D_ETH_PHY_TYPE", - "label": "Ethernet chip", - "description": "Chip used by board", - "type": "select", - "value": "TYPE_ETH_PHY_LAN8720", - "depend": { - "id": "ethernet", - "value": "true" - }, - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "LAN8720", - "value": "TYPE_ETH_PHY_LAN8720" - }, - { - "label": "TLK110", - "value": "TYPE_ETH_PHY_TLK110" - }, - { - "label": "RTL8201", - "value": "TYPE_ETH_PHY_RTL8201" - }, - { - "label": "DP83848", - "value": "TYPE_ETH_PHY_DP83848" - }, - { - "label": "DM9051", - "value": "TYPE_ETH_PHY_DM9051" - }, - { - "label": "KSZ8041", - "value": "TYPE_ETH_PHY_KSZ8041" - }, - { - "label": "KSZ8081", - "value": "TYPE_ETH_PHY_KSZ8081" - }, - { - "label": "W5500 (SPI)", - "value": "TYPE_ETH_PHY_W5500" - } - ] - }, - { - "id": "ethernetclock", - "define": "ESP3D_ETH_CLK_MODE", - "label": "Ethernet board clock mode", - "description": "Mode used by clock", - "type": "select", - "value": "MODE_ETH_CLOCK_GPIO17_OUT", - "depend": [ - { - "id": "ethernetboard", - "notvalue": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "GPIO0 IN", - "value": "MODE_ETH_CLOCK_GPIO0_IN" - }, - { - "label": "GPIO0 OUT", - "value": "MODE_ETH_CLOCK_GPIO0_OUT" - }, - { - "label": "GPIO16 OUT", - "value": "MODE_ETH_CLOCK_GPIO16_OUT" - }, - { - "label": "GPIO17 OUT", - "value": "MODE_ETH_CLOCK_GPIO17_OUT" - } - ] - }, - { - "id": "ethernetaddress", - "define": "ESP3D_ETH_PHY_ADDR", - "label": "Ethernet board address", - "description": "Address used by clock", - "type": "select", - "value": "1", - "depend": { - "id": "ethernet", - "value": "true" - }, - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "0", - "value": "0" - }, - { - "label": "1", - "value": "1" - }, - { - "label": "2", - "value": "2" - }, - { - "label": "3", - "value": "3" - }, - { - "label": "4", - "value": "4" - }, - { - "label": "5", - "value": "5" - }, - { - "label": "6", - "value": "6" - }, - { - "label": "7", - "value": "7" - }, - { - "label": "8", - "value": "8" - }, - { - "label": "9", - "value": "9" - }, - { - "label": "10", - "value": "10" - }, - { - "label": "11", - "value": "11" - }, - { - "label": "12", - "value": "12" - }, - { - "label": "13", - "value": "13" - }, - { - "label": "14", - "value": "14" - }, - { - "label": "15", - "value": "15" - }, - { - "label": "16", - "value": "16" - }, - { - "label": "17", - "value": "17" - }, - { - "label": "18", - "value": "18" - }, - { - "label": "19", - "value": "19" - }, - { - "label": "20", - "value": "20" - }, - { - "label": "21", - "value": "21" - }, - { - "label": "22", - "value": "22" - }, - { - "label": "23", - "value": "23" - }, - { - "label": "24", - "value": "24" - }, - { - "label": "25", - "value": "25" - }, - { - "label": "26", - "value": "26" - }, - { - "label": "27", - "value": "27" - }, - { - "label": "28", - "value": "28" - }, - { - "label": "29", - "value": "29" - }, - { - "label": "30", - "value": "30" - }, - { - "label": "31", - "value": "31" - } - ] - }, - { - "id": "ethernetpowerpin", - "define": "ESP3D_ETH_PHY_POWER_PIN", - "label": "Ethernet power pin", - "description": "The pin used to power ethernet chip", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "notvalue": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetmdcpin", - "define": "ESP3D_ETH_PHY_MDC_PIN", - "label": "Ethernet mdc pin", - "description": "The pin used for MDC signal", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "notvalue": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetmdiopin", - "define": "ESP3D_ETH_PHY_MDIO_PIN", - "label": "Ethernet mdio pin", - "description": "The pin used for MDIO signal", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "notvalue": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetspiirqpin", - "define": "ETH_PHY_IRQ", - "label": "Ethernet irq pin", - "description": "The pin used for irq", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "value": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetspirstpin", - "define": "ETH_PHY_RST", - "label": "Ethernet reset pin", - "description": "The pin used for reset", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "value": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetspi", - "define": "", - "label": "Ethernet Dedicated SPI", - "description": "SPI Used by Ethernet", - "type": "select", - "value": "ETHERNET_SPI_USE_SPI2", - "depend": [ - { - "id": "ethernetboard", - "value": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "SPI", - "value": "ETHERNET_SPI_USE_SPI" - }, - { - "label": "SPI2", - "value": "ETHERNET_SPI_USE_SPI2" - } - ] - }, - { - "id": "ethernetspicspin", - "define": "ETH_PHY_CS", - "label": "Ethernet SPI CS pin", - "description": "The CS pin used by dedicated SPI ethernet", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "value": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetspimosipin", - "define": "ETH_SPI_MOSI", - "label": "Ethernet SPI MOSI pin", - "description": "The MOSI pin used by dedicated SPI ethernet", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "value": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetspimisopin", - "define": "ETH_SPI_MISO", - "label": "Ethernet SPI MISO pin", - "description": "The MISO pin used by dedicated SPI ethernet", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "value": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "ethernetspisckpin", - "define": "ETH_SPI_SCK", - "label": "Ethernet SPI SCK pin", - "description": "The SCK pin used by dedicated SPI ethernet", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { - "id": "ethernetboard", - "value": "TYPE_ETH_PHY_W5500" - }, - { - "id": "ethernet", - "value": "true" - } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "bluetooth", - "define": "BLUETOOTH_FEATURE", - "label": "Use Bluetooth ", - "description": "Enable serial bluetooth communications", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true, - "depend": { - "id": "targetmcu", - "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] - } - } - ] - }, - { - "id": "channels", - "description": "The way ESP3D communicate", - "label": "Channels of ESP3D", - "setting": true, - "type": "group", - "value": [ - { - "id": "http", - "define": "HTTP_FEATURE", - "label": "Use webserver", - "description": "Enable http server", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "telnet", - "define": "TELNET_FEATURE", - "label": "Use telnet server", - "description": "Enable telnet light (raw tcp) communications", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "websocket", - "define": "WS_DATA_FEATURE", - "label": "Use Websocket server", - "description": "Enable websocket communications", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true - }, - { - "id": "notifications", - "define": "NOTIFICATION_FEATURE", - "label": "Enable notifications", - "description": "Allows to send notifications to the user", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "notificationonline", - "define": "NOTIFICATION_ESP_ONLINE", - "label": "Notification message when online", - "description": "The message that will be sent when the ESP is online", - "type": "text", - "needquote": true, - "value": "Hi, %ESP_NAME% is now online at %ESP_IP%", - "setting": true, - "depend": { "id": "notifications", "value": [true] } - }, - { - "id": "notificationtitlemsg", - "define": "ESP_NOTIFICATION_TITLE", - "label": "Notification title message", - "description": "The title of notification", - "type": "text", - "needquote": true, - "value": "ESP3D Notification", - "setting": true, - "depend": { "id": "notifications", "value": [true] } - } - ] - }, - { - "id": "discovery", - "description": "The discovery methods of ESP3D", - "label": "Discovery methods of ESP3D", - "setting": true, - "type": "group", - "value": [ - { - "id": "captive", - "define": "CAPTIVE_PORTAL_FEATURE", - "label": "Use captive portal ", - "description": "Enable captive portal in AP mode", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "mdns", - "define": "MDNS_FEATURE", - "label": "Use mDNS discovery", - "description": "This method need `bonjour` protocol on windows, or `avahi` on linux", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "ssdp", - "define": "SSDP_FEATURE", - "label": "Use Simple Service Discovery Protocol", - "description": "It is supported on Windows out of the box", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - } - ] - }, - { - "id": "customizationSSDP", - "description": "Customize your ESP3D", - "label": "SSDP Customization settings", - "setting": true, - "depend": { "id": "ssdp", "value": [true] }, - "type": "group", - "value": [ - { - "id": "ssdpmodelename32", - "define": "ESP_MODEL_NAME", - "label": "Model name", - "description": "Modele name of device", - "type": "text", - "value": "ESP32", - "needquote": true, - "depend": { - "id": "targetmcu", - "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] - }, - "setting": true - }, - { - "id": "ssdpmodelename", - "define": "ESP_MODEL_NAME", - "label": "Model name", - "description": "Modele name of device", - "type": "text", - "value": "ESP8285", - "needquote": true, - "depend": { "id": "targetmcu", "value": ["esp8285"] }, - "setting": true - }, - { - "id": "ssdpmodelename", - "define": "ESP_MODEL_NAME", - "label": "Model name", - "description": "Modele name of device", - "type": "text", - "value": "ESP8266", - "needquote": true, - "depend": { "id": "targetmcu", "value": ["esp8266"] }, - "setting": true - }, - { - "id": "ssdpmodelenamenumber", - "define": "ESP_MODEL_NUMBER", - "label": "Model number", - "description": "Modele number of device", - "type": "text", - "needquote": true, - "value": "ESP3D 3.0", - "setting": true - }, - { - "id": "ssdpmodeleurl", - "define": "ESP_MODEL_URL", - "label": "Model url", - "description": "Modele url of device", - "type": "text", - "needquote": true, - "value": "https://www.espressif.com/en/products/devkits", - "setting": true - }, - { - "id": "ssdpmodelemanufacturername", - "define": "ESP_MANUFACTURER_NAME", - "label": "Manufacturer name", - "description": "Manufacturer name of device", - "type": "text", - "needquote": true, - "value": "Espressif Systems", - "setting": true - }, - { - "id": "ssdpmodelemanufacturerurl", - "define": "ESP_MANUFACTURER_URL", - "label": "Manufacturer url", - "description": "Manufacturer url of device", - "type": "text", - "needquote": true, - "value": "https://www.espressif.com", - "setting": true - } - ] - } - ], - "filesystems": [ - { - "id": "flash", - "description": "Filesystem on flash", - "label": "Flash filesystem", - "setting": true, - "type": "group", - "value": [ - { - "id": "flashFS", - "define": "FILESYSTEM_FEATURE", - "label": "File system type", - "description": "Type of file system used by ESP3D to store files", - "type": "select", - "value": "ESP_LITTLEFS_FILESYSTEM", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "LittleFS", - "value": "ESP_LITTLEFS_FILESYSTEM" - }, - { - "label": "FatFS", - "value": "ESP_FAT_FILESYSTEM", - "help": "Only on ESP32 with large flash", - "depend": { - "id": "targetmcu", - "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] - } - } - ] - }, - { - "id": "flashTimestamp", - "define": "FILESYSTEM_TIMESTAMP_FEATURE", - "label": "Enable date/time on files", - "description": "Set date/time on files using SNTP or last webui connection", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true - } - ] - }, - { - "id": "sd", - "description": "Filesystem on SD card", - "label": "SD filesystem", - "setting": true, - "type": "group", - "depend": { "id": "formfactor", "notvalue": ["esp01"] }, - "value": [ - { - "id": "sdconnection", - "define": "SD_DEVICE_CONNECTION", - "label": "SD card connection", - "description": "Does your system has SD card and how it is connected to your ESP3D", - "type": "select", - "value": "-1", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "None", - "value": "-1", - "help": "Your ESP does not have SDcard but your system may have one" - }, - { - "label": "Direct connection", - "value": "ESP_NOT_SHARED_SD", - "help": "Only your ESP board is connected to SDCard" - }, - { - "label": "Shared connection", - "value": "ESP_SHARED_SD", - "help": "Your system has SDCard and your ESP is connected to it using sharing solution" - } - ] - }, - { - "id": "sdfs", - "define": "SD_DEVICE", - "label": "SD card library", - "description": "Library used by SD card driver", - "type": "select", - "value": "ESP_SDFAT2", - "depend": { - "id": "sdconnection", - "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] - }, - "setting": true, - "options": [ - { - "label": "Native from core", - "value": "ESP_SD_NATIVE", - "help": "No additionnal library required" - }, - { - "label": "SDFat", - "value": "ESP_SDFAT2", - "help": "SDFat 2 library required" - }, - { - "label": "SDIO", - "value": "ESP_SDIO", - "help": "Used by SDMMC", - "depend": { - "id": "targetmcu", - "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] - } - } - ] - }, - { - "id": "sdio", - "define": "SDIO_BIT_MODE", - "label": "Sdio bit mode", - "description": "Mode used by SDIO library", - "type": "select", - "value": "SD_FOUR_BIT_MODE", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, - "setting": true, - "options": [ - { - "label": "1 bit", - "value": "SD_ONE_BIT_MODE" - }, - { - "label": "4 bits", - "value": "SD_FOUR_BIT_MODE" - } - ] - }, - { - "id": "sdtimestamp", - "define": "SD_TIMESTAMP_FEATURE", - "label": "Enable date/time on files", - "description": "Set date/time on files using SNTP or last webui connection", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true - } - ] - }, - { - "id": "sdpins", - "description": "SD card pins", - "label": "SD card pins", - "setting": true, - "type": "group", - "depend": { - "id": "sdconnection", - "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] - }, - "value": [ - { - "id": "detectsdpin", - "define": "ESP_SD_DETECT_PIN", - "label": "SD card detect pin", - "description": "The pin used to detect SD card", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { - "id": "sdconnection", - "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "sddetectvalue", - "define": "ESP_SD_DETECT_VALUE", - "label": "Pin value", - "description": "State of SD card detect pin when card is present", - "type": "select", - "value": "0", - "depend": { "id": "detectsdpin", "notvalue": ["-1"] }, - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "High", - "value": "1" - }, - { - "label": "Low", - "value": "0" - } - ] - }, - { - "id": "shareflagsdpin", - "define": "ESP_FLAG_SHARED_SD_PIN", - "label": "SD shared flag pin", - "description": "The pin used to enable SD card for ESP board", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { - "id": "sdconnection", - "value": ["ESP_SHARED_SD"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "shareflagsdvalue", - "define": "ESP_FLAG_SHARED_SD_VALUE", - "label": "Pin value", - "description": "State of SD card share pin for ESP board", - "type": "select", - "value": "0", - "depend": { "id": "shareflagsdpin", "notvalue": ["-1"] }, - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "High", - "value": "1" - }, - { - "label": "Low", - "value": "0" - } - ] - }, - { - "id": "sdcspin", - "define": "ESP_SD_CS_PIN", - "label": "SD card CS pin", - "description": "The pin used to select SD card in SPI mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdmosipin", - "define": "ESP_SD_MOSI_PIN", - "label": "SD card Mosi pin", - "description": "The Mosi pin used by SD in SPI mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, - { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdmisopin", - "define": "ESP_SD_MISO_PIN", - "label": "SD card Miso pin", - "description": "The Miso pin used by SD in SPI mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, - { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdsckpin", - "define": "ESP_SD_SCK_PIN", - "label": "SD card Sck pin", - "description": "The Sck pin used by SD in SPI mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, - { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdioclkpin", - "define": "ESP_SDIO_CLK_PIN", - "label": "SDIO card clk pin", - "description": "The clk pin used by SD card in SDIO mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdiocmdpin", - "define": "ESP_SDIO_CMD_PIN", - "label": "SDIO card cmd pin", - "description": "The cmd pin used by SD card in SDIO mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdiod0pin", - "define": "ESP_SDIO_D0_PIN", - "label": "SDIO card data 0 pin", - "description": "The d0 pin used by SD card in SDIO mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdiod1pin", - "define": "ESP_SDIO_D1_PIN", - "label": "SDIO card data 1 pin", - "description": "The d1 pin used by SD card in SDIO mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { "id": "sdfs", "value": ["ESP_SDIO"] }, - { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdiod2pin", - "define": "ESP_SDIO_D2_PIN", - "label": "SDIO card data 2 pin", - "description": "The d2 pin used by SD card in SDIO mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { "id": "sdfs", "value": ["ESP_SDIO"] }, - { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } - ], - "setting": true, - "usedescforoptions": true - }, - { - "id": "sdiod3pin", - "define": "ESP_SDIO_D3_PIN", - "label": "SDIO card data 3 pin", - "description": "The d3 pin used by SD card in SDIO mode", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": [ - { "id": "sdfs", "value": ["ESP_SDIO"] }, - { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } - ], - "setting": true, - "usedescforoptions": true - } - ] - }, - { - "id": "remote", - "description": "Remote filesystem access", - "label": "Remote access", - "setting": true, - "type": "group", - "value": [ - { - "id": "globalFS", - "define": "GLOBAL_FILESYSTEM_FEATURE", - "label": "Enable global filesystem", - "description": "Allows to access to all filesystems fromsame location", - "type": "boolean", - "value": false, - "depend": { "id": "sdconnection", "notvalue": ["-1"] }, - "disableiffalse": true, - "setting": true - }, - { - "id": "webdav", - "define": "WEBDAV_FEATURE", - "label": "WebDav access", - "description": "Use WebDav to access to your filesystem", - "type": "select", - "value": "-1", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "None", - "value": "-1" - }, - { - "label": "All filesystems", - "value": "FS_ROOT", - "depend": { "id": "globalFS", "value": [true] } - }, - { - "label": "ESP3D Flash", - "value": "FS_FLASH" - }, - { - "label": "ESP3D SD", - "value": "FS_SD", - "depend": { - "id": "sdconnection", - "notvalue": ["-1"] - } - } - ] - }, - { - "id": "ftp", - "define": "FTP_FEATURE", - "label": "FTP access", - "description": "Use FTP to access to your filesystem (1 connection only)", - "type": "select", - "value": "-1", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "None", - "value": "-1" - }, - { - "label": "All filesystems", - "value": "FS_ROOT", - "depend": { "id": "globalFS", "value": [true] } - }, - { - "label": "ESP3D Flash", - "value": "FS_FLASH" - }, - { - "label": "ESP3D SD", - "value": "FS_SD", - "depend": { - "id": "sdconnection", - "notvalue": ["-1"] - } - } - ] - } - ] - } - ], - "update": [ - { - "id": "reset", - "description": "Reset ESP3D settings", - "label": "Reset ESP3D", - "setting": true, - "type": "group", - "value": [ - { - "id": "resetfeature", - "define": "PIN_RESET_FEATURE", - "label": "Enable pin reset feature", - "description": "Use a pin to reset ESP3D settings", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": false, - "hide": true - }, - { - "id": "resetpin", - "define": "ESP3D_RESET_PIN", - "label": "Reset pin", - "description": "The pin used to reset ESP3D setting if set to low for more than 1 second at start", - "type": "select", - "ispin": true, - "header": "#define PIN_RESET_FEATURE\n", - "depend": { "id": "resetfeature", "value": [true] }, - "value": "-1", - "setting": true, - "usedescforoptions": true - } - ] - }, - { - "id": "update", - "description": "Update ESP3D firmware", - "label": "Update ESP3D", - "setting": true, - "depend": { "id": "targetflashsize", "notvalue": ["1", "2"] }, - "type": "group", - "value": [ - { - "id": "ota", - "define": "OTA_FEATURE", - "label": "Enable OTA", - "description": "Over The Air Update (OTA)", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true - }, - { - "id": "webupdate", - "define": "WEB_UPDATE_FEATURE", - "label": "Enable Web Update", - "description": "Update firmware using WebUI", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "sdupdate", - "define": "SD_UPDATE_FEATURE", - "label": "Enable SD card Update", - "description": "Update firmware and settings using file on SDCard", - "type": "boolean", - "depend": { "id": "sdconnection", "notvalue": ["-1"] }, - "value": true, - "disableiffalse": true, - "setting": true - } - ] - } - ], - "devices": [ - { - "id": "screens", - "description": "Rendering screens", - "label": "Display settings", - - "setting": true, - "type": "group", - "value": [ - { - "id": "printerscreen", - "define": "PRINTER_HAS_DISPLAY", - "label": "Printer screen", - "description": "Your printer has a display", - "type": "boolean", - "value": true, - "depend": { "id": "systemtype", "value": ["Printer3D"] }, - "disableiffalse": true, - "setting": true - }, - { - "id": "displaytype", - "define": "DISPLAY_DEVICE", - "label": "ESP3D screen", - "description": "Screen connected to ESP board", - "type": "select", - "value": "-1", - "setting": true, - "usedescforoptions": true, - "depend": { "id": "formfactor", "notvalue": ["esp01"] }, - "options": [ - { - "label": "None", - "value": "-1" - }, - { - "label": "OLED I2C SSD1306 128X64", - "value": "OLED_I2C_SSD1306_128X64" - }, - { - "label": "OLED I2C SSDSH1106 132X64", - "value": "OLED_I2C_SSDSH1106_132X64" - }, - { - "label": "TFT SPI ST7789 240X240", - "value": "TFT_SPI_ST7789_240X240" - }, - { - "label": "TFT SPI ST7789 135X240", - "value": "TFT_SPI_ST7789_135X240" - } - ] - }, - { - "id": "flipdisplay", - "define": "DISPLAY_FLIP_VERTICALY", - "label": "Flip screen", - "description": "Flip/rotate screen", - "type": "boolean", - "value": false, - "depend": { "id": "displaytype", "notvalue": ["-1"] }, - "disableiffalse": true, - "setting": true - }, - { - "id": "i2caddressoled", - "define": "DISPLAY_I2C_ADDR", - "label": "Display i2C address", - "description": "Wire address of display", - "type": "text", - "value": "0x3c", - "depend": { - "id": "displaytype", - "value": ["OLED_I2C_SSD1306_128X64", "OLED_I2C_SSDSH1106_132X64"] - }, - "setting": true - }, - { - "id": "oledrstpin", - "define": "DISPLAY_I2C_PIN_RST", - "label": "Display reset pin", - "description": "The pin used to reset the screen (optional)", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { - "id": "displaytype", - "value": ["OLED_I2C_SSD1306_128X64", "OLED_I2C_SSDSH1106_132X64"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "tftledpin", - "define": "DISPLAY_LED_PIN", - "label": "TFT led pin", - "description": "The pin used for the backlight", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { - "id": "displaytype", - "value": ["TFT_SPI_ST7789_135X240", "TFT_SPI_ST7789_240X240"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "tftDCpin", - "define": "DISPLAY_DC_PIN", - "label": "TFT DC pin", - "description": "The dc pin used for the display", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { - "id": "displaytype", - "value": ["TFT_SPI_ST7789_135X240", "TFT_SPI_ST7789_240X240"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "tftRSTpin", - "define": "DISPLAY_RST_PIN", - "label": "TFT RST pin", - "description": "The pin used to reset display", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { - "id": "displaytype", - "value": ["TFT_SPI_ST7789_135X240", "TFT_SPI_ST7789_240X240"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "tftCSpin", - "define": "DISPLAY_CS_PIN", - "label": "TFT CS pin", - "description": "The CS pin used by display", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": { - "id": "displaytype", - "value": ["TFT_SPI_ST7789_135X240", "TFT_SPI_ST7789_240X240"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "tftMOSIpin", - "define": "DISPLAY_MOSI_PIN", - "label": "TFT MOSI pin", - "description": "The MOSI pin used by display", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": { - "id": "displaytype", - "value": ["TFT_SPI_ST7789_135X240", "TFT_SPI_ST7789_240X240"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "tftSCKpin", - "define": "DISPLAY_SCK_PIN", - "label": "TFT SCK pin", - "description": "The SCK pin used by display", - "type": "select", - "ispin": true, - "usedefault": true, - "value": "-1", - "depend": { - "id": "displaytype", - "value": ["TFT_SPI_ST7789_135X240", "TFT_SPI_ST7789_240X240"] - }, - "setting": true, - "usedescforoptions": true - } - ] - }, - { - "id": "buzzer", - "description": "Buzzer feature", - "label": "Audio settings", - "setting": true, - "type": "group", - "value": [ - { - "id": "buzzerfeature", - "define": "BUZZER_DEVICE", - "label": "Enable buzzer", - "description": "Your esp board has a passive buzzer", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true - }, - { - "id": "buzzerpin", - "define": "ESP3D_BUZZER_PIN", - "label": "Buzzer pin", - "description": "The pin used for the passive buzzer", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { "id": "buzzerfeature", "value": [true] }, - "setting": true, - "usedescforoptions": true - } - ] - }, - { - "id": "sensor", - "description": "Sensor feature", - "label": "Sensor settings", - "setting": true, - "type": "group", - "value": [ - { - "id": "sensortype", - "define": "SENSOR_DEVICE", - "label": "ESP3D connected sensor", - "description": "Sensor connected to ESP board", - "type": "select", - "value": "-1", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "None", - "value": "-1" - }, - { - "label": "DHT11", - "value": "DHT11_DEVICE" - }, - { - "label": "DHT22", - "value": "DHT22_DEVICE" - }, - { - "label": "BMP280", - "value": "BMP280_DEVICE" - }, - { - "label": "BME280", - "value": "BME280_DEVICE" - }, - { - "label": "Analog sensor", - "value": "ANALOG_DEVICE" - } - ] - }, - { - "id": "i2caddresssensor", - "define": "SENSOR_ADDR", - "label": "Sensor i2C address", - "description": "Wire address of sensor", - "needquote": false, - "type": "text", - "value": "0x76", - "depend": { - "id": "sensortype", - "notvalue": ["-1", "ANALOG_DEVICE", "DHT11_DEVICE", "DHT22_DEVICE"] - }, - "setting": true - }, - { - "id": "sensorpin", - "define": "ESP3D_SENSOR_PIN", - "label": "Sensor pin", - "description": "The pin used for the sensor", - "type": "select", - "ispin": true, - "value": "-1", - "depend": { - "id": "sensortype", - "notvalue": ["-1", "BMP280_DEVICE", "BME280_DEVICE"] - }, - "setting": true, - "usedescforoptions": true - }, - { - "id": "analogsensorconverter", - "define": "SENSOR_CONVERTER(v)", - "label": "Conversion formula", - "description": "v is the value read from sensor", - "type": "text", - "value": "v*0.588", - "depend": { - "id": "sensortype", - "value": ["ANALOG_DEVICE"] - }, - "setting": true - }, - { - "id": "sensorunit", - "define": "SENSOR__UNIT", - "label": "Unit", - "description": "Unit of the sensor result", - "type": "select", - "depend": { "id": "sensortype", "notvalue": ["-1"] }, - "value": "C", - "usedescforoptions": true, - "options": [ - { - "label": "C", - "value": "\"C\"" - }, - { - "label": "K", - "value": "\"K\"" - }, - { - "label": "V", - "value": "\"V\"", - "depend": { - "id": "sensortype", - "value": ["ANALOG_DEVICE"] - } - } - ], - "setting": true - } - ] - }, - { - "id": "camera", - "description": "Connected camera", - "label": "Camera settings", - "setting": true, - "depend": [ - { "id": "targetmcu", "value": ["esp32", "esp32s2", "esp32s3"] }, - { "id": "formfactor", "notvalue": ["esp01"] }, - { "id": "has_psram", "value": [true] } - ], - "type": "group", - "value": [ - { - "id": "cameratype", - "define": "CAMERA_DEVICE", - "label": "Camera type", - "description": "Camera connected to ESP board, only ones with PSRAM are supported", - "type": "select", - "value": "-1", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "None", - "value": "-1" - }, - { - "label": "AI Thinker (ESP32-Cam)", - "value": "CAMERA_MODEL_AI_THINKER", - "depend": { "id": "targetmcu", "value": ["esp32"] } - }, - { - "label": "ESP-Eye", - "value": "CAMERA_MODEL_ESP_EYE", - "depend": { "id": "targetmcu", "value": ["esp32"] } - }, - { - "label": "ESP-Eye", - "value": "CAMERA_MODEL_ESP32S3_EYE", - "depend": { - "id": "targetmcu", - "value": ["esp32s3"] - } - }, - { - "label": "M5Stack", - "value": "CAMERA_MODEL_M5STACK_PSRAM", - "depend": { "id": "targetmcu", "value": ["esp32"] } - }, - { - "label": "M5Stack V2", - "value": "CAMERA_MODEL_M5STACK_V2_PSRAM", - "depend": { "id": "targetmcu", "value": ["esp32"] } - }, - { - "label": "Wrover Kit", - "value": "CAMERA_MODEL_WROVER_KIT", - "depend": { "id": "targetmcu", "value": ["esp32"] } - }, - { - "label": "Espressif Camera", - "value": "CAMERA_MODEL_ESP32_CAM_BOARD", - "depend": { "id": "targetmcu", "value": ["esp32"] } - }, - { - "label": "Espressif Camera", - "value": "CAMERA_MODEL_ESP32S2_CAM_BOARD", - "depend": { - "id": "targetmcu", - "value": ["esp32s2"] - } - }, - { - "label": "Espressif Camera", - "value": "CAMERA_MODEL_ESP32S3_CAM_LCD", - "depend": { - "id": "targetmcu", - "value": ["esp32s3"] - } - }, - { - "label": "Seeed Studio Xiao S3 Sense Camera", - "value": "CAMERA_MODEL_XIAO_ESP32S3", - "depend": { - "id": "targetmcu", - "value": ["esp32s3"] - } - } - ] - }, - { - "id": "cameraflipverticaly", - "define": "CAMERA_DEVICE_FLIP_VERTICALY", - "label": "Flip vertically", - "description": "Flip camera verticaly", - "type": "boolean", - "depend": { "id": "cameratype", "notvalue": ["-1"] }, - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "camerafliphorizontaly", - "define": "CAMERA_DEVICE_FLIP_HORIZONTALY", - "label": "Flip horizontally", - "description": "Flip camera horizontally", - "type": "boolean", - "depend": { "id": "cameratype", "notvalue": ["-1"] }, - "value": true, - "disableiffalse": true, - "setting": true - } - ] - } - ], - "security": [ - { - "id": "security", - "description": "How commands are allowed to be sent to ESP3D", - "label": "Levels of security", - "setting": true, - "type": "group", - "value": [ - { - "id": "serialcmdfeature", - "define": "SERIAL_COMMAND_FEATURE", - "label": "Enable serial commands", - "description": "Allow commands to be sent to ESP3D via serial port", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - }, - { - "id": "corsfeature", - "define": "ESP_ACCESS_CONTROL_ALLOW_ORIGIN", - "label": "Enable cross origin access", - "description": "Allow to show the camera in web page different than device web server", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true - }, - { - "id": "authentication", - "define": "AUTHENTICATION_FEATURE", - "label": "Enable authentication", - "description": "Force usage of authentication for commands", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true - } - ] - } - ], - "others": [ - { - "id": "additional", - "description": "Extra settings", - "label": "Additional features", - "setting": true, - "type": "group", - "value": [ - { - "id": "directpins", - "define": "DIRECT_PIN_FEATURE", - "label": "Enable direct control pin", - "description": "Controls pins using [ESP201]", - "type": "boolean", - "value": true, - "disableiffalse": true, - "setting": true - } - ] - }, - { - "id": "scripting", - "description": "Scripting on ESP3D", - "label": "Scripting settings", - "setting": true, - "type": "group", - "value": [ - { - "id": "autorunscript", - "define": "AUTOSTART_SCRIPT_FEATURE", - "label": "Enable Autostart script", - "description": "Automaticaly launch script when ESP3D is started", - "setting": true, - "type": "select", - "value": "-1", - "usedescforoptions": true, - "options": [ - { - "label": "None", - "value": "-1" - }, - { - "label": "Commands", - "value": "\"script\"" - }, - { - "label": "File", - "value": "\"file\"" - } - ] - }, - { - "id": "autostartcommands", - "define": "ESP_AUTOSTART_SCRIPT", - "label": "Commands to run on startup", - "description": "Separate commands with ';'", - "depend": { - "id": "autorunscript", - "value": ["\"script\""] - }, - "type": "text", - "needquote": true, - "value": "M117 Mounting SD;M21", - "disableiffalse": true, - "setting": true - }, - { - "id": "autostartfile", - "define": "ESP_AUTOSTART_SCRIPT_FILE", - "label": "File to run on startup", - "description": "The file should be on ESP3D filesystem", - "depend": { "id": "autorunscript", "value": ["\"file\""] }, - "type": "text", - "needquote": true, - "value": "autorun.gco", - "disableiffalse": true, - "setting": true - }, - { - "id": "luainterpreter", - "define": "ESP_LUA_INTERPRETER_FEATURE", - "label": "Enable lua interpreter", - "description": "Allow to use lua interpreter on ESP3D", - "type": "boolean", - "value": false, - "disableiffalse": true, - "setting": true, - "depend": { "id": "targetmcu", "notvalue": ["esp8285", "esp8266"] } - }, - { - "id": "hook_ip", - "label": "Enable hook when getting IP", - "description": "Automaticaly launch script when get IP", - "setting": true, - "type": "boolean", - "value": false, - "disableiffalse": true - }, - { - "id": "get_ip_hook", - "define": "ESP_GOT_IP_HOOK", - "label": "Commands to run when got IP", - "description": "Separate commands with ';'", - "depend": { - "id": "hook_ip", - "value": [true] - }, - "type": "text", - "needquote": true, - "value": "[ESP212]IP:%ESP_IP%", - "setting": true - }, - { - "id": "hook_datetime", - "label": "Enable hook when getting date/time", - "description": "Automaticaly launch script when get date/time", - "setting": true, - "type": "boolean", - "value": false, - "disableiffalse": true - }, - { - "id": "get_datetime_hook", - "define": "ESP_GOT_DATE_TIME_HOOK", - "label": "Commands to run when got date/time", - "description": "Separate commands with ';'", - "depend": { - "id": "hook_datetime", - "value": [true] - }, - "type": "text", - "needquote": true, - "value": "[ESP212]DATE:%ESP_DATETIME%", - "disableiffalse": true, - "setting": true - } - ] - } - ], - "default": [ - { - "id": "default", - "description": "Do not edit them", - "label": "Default settings for ESP3D", - "setting": true, - "type": "group", - "value": [ - { - "id": "gcodehost", - "define": "GCODE_HOST_FEATURE", - "label": "Gcode Host Feature", - "description": "This feature allows to process Gcode files like macros.", - "type": "boolean", - "value": true, - "setting": true, - "disableiffalse": true - }, - { - "id": "settings", - "define": "ESP_SAVE_SETTINGS", - "label": "Settings location", - "description": "Location where ESP3D will save settings", - "type": "select", - "value": "SETTINGS_IN_EEPROM", - "setting": true, - "usedescforoptions": true, - "options": [ - { - "label": "EEPROM", - "value": "SETTINGS_IN_EEPROM" - }, - { - "label": "NFS", - "value": "SETTINGS_IN_PREFERENCES" - } - ] - } - ] - } - ] -} diff --git a/src/configuration/index.js b/src/configuration/index.js new file mode 100644 index 0000000..29d237f --- /dev/null +++ b/src/configuration/index.js @@ -0,0 +1,21 @@ +import featuresConfiguration from "./tabs/features" +import networkConfiguration from "./tabs/network" +import filesystemsConfiguration from "./tabs/filesystems" +import updateConfiguration from "./tabs/update" +import devicesConfiguration from "./tabs/devices" +import securityConfiguration from "./tabs/security" +import othersConfiguration from "./tabs/others" +import defaultsConfiguration from "./tabs/defaults" + +const defaultConfiguration = { + features: featuresConfiguration, + network: networkConfiguration, + filesystems: filesystemsConfiguration, + update: updateConfiguration, + devices: devicesConfiguration, + security: securityConfiguration, + others: othersConfiguration, + default: defaultsConfiguration, +} + +export default defaultConfiguration diff --git a/src/configuration/tabs/defaults.js b/src/configuration/tabs/defaults.js new file mode 100644 index 0000000..d2f2550 --- /dev/null +++ b/src/configuration/tabs/defaults.js @@ -0,0 +1,3 @@ +import defaultConfiguration from "./defaults.json" + +export default defaultConfiguration diff --git a/src/configuration/tabs/defaults.json b/src/configuration/tabs/defaults.json new file mode 100644 index 0000000..b9f18a6 --- /dev/null +++ b/src/configuration/tabs/defaults.json @@ -0,0 +1,41 @@ +[ + { + "id": "default", + "description": "Do not edit them", + "label": "Default settings for ESP3D", + "setting": true, + "type": "group", + "value": [ + { + "id": "gcodehost", + "define": "GCODE_HOST_FEATURE", + "label": "Gcode Host Feature", + "description": "This feature allows to process Gcode files like macros.", + "type": "boolean", + "value": true, + "setting": true, + "disableiffalse": true + }, + { + "id": "settings", + "define": "ESP_SAVE_SETTINGS", + "label": "Settings location", + "description": "Location where ESP3D will save settings", + "type": "select", + "value": "SETTINGS_IN_EEPROM", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "EEPROM", + "value": "SETTINGS_IN_EEPROM" + }, + { + "label": "NFS", + "value": "SETTINGS_IN_PREFERENCES" + } + ] + } + ] + } +] diff --git a/src/configuration/tabs/devices.js b/src/configuration/tabs/devices.js new file mode 100644 index 0000000..2c887c1 --- /dev/null +++ b/src/configuration/tabs/devices.js @@ -0,0 +1,3 @@ +import devicesConfiguration from "./devices.json" + +export default devicesConfiguration diff --git a/src/configuration/tabs/devices.json b/src/configuration/tabs/devices.json new file mode 100644 index 0000000..a72d962 --- /dev/null +++ b/src/configuration/tabs/devices.json @@ -0,0 +1,575 @@ +[ + { + "id": "screens", + "description": "Rendering screens", + "label": "Display settings", + "setting": true, + "type": "group", + "value": [ + { + "id": "printerscreen", + "define": "PRINTER_HAS_DISPLAY", + "label": "Printer screen", + "description": "Your printer has a display", + "type": "boolean", + "value": true, + "depend": { + "id": "systemtype", + "value": [ + "Printer3D" + ] + }, + "disableiffalse": true, + "setting": true + }, + { + "id": "displaytype", + "define": "DISPLAY_DEVICE", + "label": "ESP3D screen", + "description": "Screen connected to ESP board", + "type": "select", + "value": "-1", + "setting": true, + "usedescforoptions": true, + "depend": { + "id": "formfactor", + "notvalue": [ + "esp01" + ] + }, + "options": [ + { + "label": "None", + "value": "-1" + }, + { + "label": "OLED I2C SSD1306 128X64", + "value": "OLED_I2C_SSD1306_128X64" + }, + { + "label": "OLED I2C SSDSH1106 132X64", + "value": "OLED_I2C_SSDSH1106_132X64" + }, + { + "label": "TFT SPI ST7789 240X240", + "value": "TFT_SPI_ST7789_240X240" + }, + { + "label": "TFT SPI ST7789 135X240", + "value": "TFT_SPI_ST7789_135X240" + } + ] + }, + { + "id": "flipdisplay", + "define": "DISPLAY_FLIP_VERTICALY", + "label": "Flip screen", + "description": "Flip/rotate screen", + "type": "boolean", + "value": false, + "depend": { + "id": "displaytype", + "notvalue": [ + "-1" + ] + }, + "disableiffalse": true, + "setting": true + }, + { + "id": "i2caddressoled", + "define": "DISPLAY_I2C_ADDR", + "label": "Display i2C address", + "description": "Wire address of display", + "type": "text", + "value": "0x3c", + "depend": { + "id": "displaytype", + "value": [ + "OLED_I2C_SSD1306_128X64", + "OLED_I2C_SSDSH1106_132X64" + ] + }, + "setting": true + }, + { + "id": "oledrstpin", + "define": "DISPLAY_I2C_PIN_RST", + "label": "Display reset pin", + "description": "The pin used to reset the screen (optional)", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "displaytype", + "value": [ + "OLED_I2C_SSD1306_128X64", + "OLED_I2C_SSDSH1106_132X64" + ] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "tftledpin", + "define": "DISPLAY_LED_PIN", + "label": "TFT led pin", + "description": "The pin used for the backlight", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "displaytype", + "value": [ + "TFT_SPI_ST7789_135X240", + "TFT_SPI_ST7789_240X240" + ] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "tftDCpin", + "define": "DISPLAY_DC_PIN", + "label": "TFT DC pin", + "description": "The dc pin used for the display", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "displaytype", + "value": [ + "TFT_SPI_ST7789_135X240", + "TFT_SPI_ST7789_240X240" + ] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "tftRSTpin", + "define": "DISPLAY_RST_PIN", + "label": "TFT RST pin", + "description": "The pin used to reset display", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "displaytype", + "value": [ + "TFT_SPI_ST7789_135X240", + "TFT_SPI_ST7789_240X240" + ] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "tftCSpin", + "define": "DISPLAY_CS_PIN", + "label": "TFT CS pin", + "description": "The CS pin used by display", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": { + "id": "displaytype", + "value": [ + "TFT_SPI_ST7789_135X240", + "TFT_SPI_ST7789_240X240" + ] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "tftMOSIpin", + "define": "DISPLAY_MOSI_PIN", + "label": "TFT MOSI pin", + "description": "The MOSI pin used by display", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": { + "id": "displaytype", + "value": [ + "TFT_SPI_ST7789_135X240", + "TFT_SPI_ST7789_240X240" + ] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "tftSCKpin", + "define": "DISPLAY_SCK_PIN", + "label": "TFT SCK pin", + "description": "The SCK pin used by display", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": { + "id": "displaytype", + "value": [ + "TFT_SPI_ST7789_135X240", + "TFT_SPI_ST7789_240X240" + ] + }, + "setting": true, + "usedescforoptions": true + } + ] + }, + { + "id": "buzzer", + "description": "Buzzer feature", + "label": "Audio settings", + "setting": true, + "type": "group", + "value": [ + { + "id": "buzzerfeature", + "define": "BUZZER_DEVICE", + "label": "Enable buzzer", + "description": "Your esp board has a passive buzzer", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true + }, + { + "id": "buzzerpin", + "define": "ESP3D_BUZZER_PIN", + "label": "Buzzer pin", + "description": "The pin used for the passive buzzer", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "buzzerfeature", + "value": [ + true + ] + }, + "setting": true, + "usedescforoptions": true + } + ] + }, + { + "id": "sensor", + "description": "Sensor feature", + "label": "Sensor settings", + "setting": true, + "type": "group", + "value": [ + { + "id": "sensortype", + "define": "SENSOR_DEVICE", + "label": "ESP3D connected sensor", + "description": "Sensor connected to ESP board", + "type": "select", + "value": "-1", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "None", + "value": "-1" + }, + { + "label": "DHT11", + "value": "DHT11_DEVICE" + }, + { + "label": "DHT22", + "value": "DHT22_DEVICE" + }, + { + "label": "BMP280", + "value": "BMP280_DEVICE" + }, + { + "label": "BME280", + "value": "BME280_DEVICE" + }, + { + "label": "Analog sensor", + "value": "ANALOG_DEVICE" + } + ] + }, + { + "id": "i2caddresssensor", + "define": "SENSOR_ADDR", + "label": "Sensor i2C address", + "description": "Wire address of sensor", + "needquote": false, + "type": "text", + "value": "0x76", + "depend": { + "id": "sensortype", + "notvalue": [ + "-1", + "ANALOG_DEVICE", + "DHT11_DEVICE", + "DHT22_DEVICE" + ] + }, + "setting": true + }, + { + "id": "sensorpin", + "define": "ESP3D_SENSOR_PIN", + "label": "Sensor pin", + "description": "The pin used for the sensor", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "sensortype", + "notvalue": [ + "-1", + "BMP280_DEVICE", + "BME280_DEVICE" + ] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "analogsensorconverter", + "define": "SENSOR_CONVERTER(v)", + "label": "Conversion formula", + "description": "v is the value read from sensor", + "type": "text", + "value": "v*0.588", + "depend": { + "id": "sensortype", + "value": [ + "ANALOG_DEVICE" + ] + }, + "setting": true + }, + { + "id": "sensorunit", + "define": "SENSOR__UNIT", + "label": "Unit", + "description": "Unit of the sensor result", + "type": "select", + "depend": { + "id": "sensortype", + "notvalue": [ + "-1" + ] + }, + "value": "C", + "usedescforoptions": true, + "options": [ + { + "label": "C", + "value": "\"C\"" + }, + { + "label": "K", + "value": "\"K\"" + }, + { + "label": "V", + "value": "\"V\"", + "depend": { + "id": "sensortype", + "value": [ + "ANALOG_DEVICE" + ] + } + } + ], + "setting": true + } + ] + }, + { + "id": "camera", + "description": "Connected camera", + "label": "Camera settings", + "setting": true, + "depend": [ + { + "id": "targetmcu", + "value": [ + "esp32", + "esp32s2", + "esp32s3" + ] + }, + { + "id": "formfactor", + "notvalue": [ + "esp01" + ] + }, + { + "id": "has_psram", + "value": [ + true + ] + } + ], + "type": "group", + "value": [ + { + "id": "cameratype", + "define": "CAMERA_DEVICE", + "label": "Camera type", + "description": "Camera connected to ESP board, only ones with PSRAM are supported", + "type": "select", + "value": "-1", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "None", + "value": "-1" + }, + { + "label": "AI Thinker (ESP32-Cam)", + "value": "CAMERA_MODEL_AI_THINKER", + "depend": { + "id": "targetmcu", + "value": [ + "esp32" + ] + } + }, + { + "label": "ESP-Eye", + "value": "CAMERA_MODEL_ESP_EYE", + "depend": { + "id": "targetmcu", + "value": [ + "esp32" + ] + } + }, + { + "label": "ESP-Eye", + "value": "CAMERA_MODEL_ESP32S3_EYE", + "depend": { + "id": "targetmcu", + "value": [ + "esp32s3" + ] + } + }, + { + "label": "M5Stack", + "value": "CAMERA_MODEL_M5STACK_PSRAM", + "depend": { + "id": "targetmcu", + "value": [ + "esp32" + ] + } + }, + { + "label": "M5Stack V2", + "value": "CAMERA_MODEL_M5STACK_V2_PSRAM", + "depend": { + "id": "targetmcu", + "value": [ + "esp32" + ] + } + }, + { + "label": "Wrover Kit", + "value": "CAMERA_MODEL_WROVER_KIT", + "depend": { + "id": "targetmcu", + "value": [ + "esp32" + ] + } + }, + { + "label": "Espressif Camera", + "value": "CAMERA_MODEL_ESP32_CAM_BOARD", + "depend": { + "id": "targetmcu", + "value": [ + "esp32" + ] + } + }, + { + "label": "Espressif Camera", + "value": "CAMERA_MODEL_ESP32S2_CAM_BOARD", + "depend": { + "id": "targetmcu", + "value": [ + "esp32s2" + ] + } + }, + { + "label": "Espressif Camera", + "value": "CAMERA_MODEL_ESP32S3_CAM_LCD", + "depend": { + "id": "targetmcu", + "value": [ + "esp32s3" + ] + } + }, + { + "label": "Seeed Studio Xiao S3 Sense Camera", + "value": "CAMERA_MODEL_XIAO_ESP32S3", + "depend": { + "id": "targetmcu", + "value": [ + "esp32s3" + ] + } + } + ] + }, + { + "id": "cameraflipverticaly", + "define": "CAMERA_DEVICE_FLIP_VERTICALY", + "label": "Flip vertically", + "description": "Flip camera verticaly", + "type": "boolean", + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "camerafliphorizontaly", + "define": "CAMERA_DEVICE_FLIP_HORIZONTALY", + "label": "Flip horizontally", + "description": "Flip camera horizontally", + "type": "boolean", + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "value": true, + "disableiffalse": true, + "setting": true + } + ] + } +] diff --git a/src/configuration/tabs/features.js b/src/configuration/tabs/features.js new file mode 100644 index 0000000..32dd38d --- /dev/null +++ b/src/configuration/tabs/features.js @@ -0,0 +1,3 @@ +import featuresConfiguration from "./features.json" + +export default featuresConfiguration diff --git a/src/configuration/tabs/features.json b/src/configuration/tabs/features.json new file mode 100644 index 0000000..32c1b39 --- /dev/null +++ b/src/configuration/tabs/features.json @@ -0,0 +1,486 @@ +[ + { + "id": "mcu", + "description": "ESP Board using ESP3D firmware", + "label": "Board description", + "setting": false, + "type": "group", + "value": [ + { + "id": "targetmcu", + "define": "ESP_MCU", + "description": "MCU", + "label": "MCU", + "type": "select", + "value": "esp32", + "setting": false, + "options": [ + { + "label": "ESP8285", + "value": "esp8285" + }, + { + "label": "ESP8266", + "value": "esp8266" + }, + { + "label": "ESP32", + "value": "esp32" + }, + { + "label": "ESP32-S2", + "value": "esp32s2" + }, + { + "label": "ESP32-S3", + "value": "esp32s3" + }, + { + "label": "ESP32-C3", + "value": "esp32c3" + }, + { + "label": "ESP32-C6", + "value": "esp32c6" + } + ] + }, + { + "id": "targetflashsize", + "define": "FLASH_SIZE", + "description": "ESP flash size", + "label": "ESP flash size", + "type": "select", + "value": "4", + "setting": false, + "options": [ + { + "label": "1MB", + "value": "1", + "depend": { + "id": "targetmcu", + "value": [ + "esp8266", + "esp8285" + ] + }, + "help": "No OTA/ Web update possible" + }, + { + "label": "2MB", + "value": "2", + "help": "No OTA/ Web update possible" + }, + { + "label": "4MB", + "value": "4" + }, + { + "label": "8MB", + "value": "8" + }, + { + "label": "16MB", + "value": "16" + } + ] + }, + { + "id": "formfactor", + "define": "ESP_FORM_FACTOR", + "description": "Pins form factor", + "label": "Form factor", + "type": "select", + "value": "original", + "setting": false, + "options": [ + { + "label": "Original (all pins)", + "value": "original" + }, + { + "label": "ESP01 (8 pins)", + "value": "esp01" + } + ] + }, + { + "id": "flash_mod1", + "define": "flash_mode", + "description": "Flash mode", + "label": "Flash mode", + "type": "select", + "value": "dio", + "depend": { + "id": "targetmcu", + "value": [ + "esp8266", + "esp8285" + ] + }, + "setting": false, + "options": [ + { + "label": "qio", + "value": "qio" + }, + { + "label": "dio", + "value": "dio" + }, + { + "label": "dout", + "value": "dout" + }, + { + "label": "qout", + "value": "qout" + } + ] + }, + { + "id": "flash_mode", + "define": "flash_mode", + "description": "Flash mode", + "label": "Flash mode", + "type": "select", + "value": "qio", + "depend": { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + }, + "setting": false, + "options": [ + { + "label": "qio", + "value": "qio" + }, + { + "label": "dio", + "value": "dio" + }, + { + "label": "dout", + "value": "dout" + }, + { + "label": "qout", + "value": "qout" + } + ] + }, + { + "id": "resetmethod", + "define": "upload_resetmethod", + "description": "Reset method for upload", + "label": "Reset method for upload", + "type": "select", + "depend": { + "id": "targetmcu", + "value": [ + "esp8266", + "esp8285" + ] + }, + "value": "ck", + "setting": false, + "options": [ + { + "label": "ck", + "value": "ck", + "help": "Need to manually set GPIO 0 to gnd" + }, + { + "label": "nodemcu", + "value": "nodemcu", + "help": "Automatically set GPIO 0 to gnd via 2 NPN transistors" + }, + { + "label": "wifio", + "value": "wifio", + "help": "Automatically set GPIO 0 to gnd via TX and DTR controls" + } + ] + }, + { + "id": "has_psram", + "define": "BOARD_HAS_PSRAM", + "description": "Has External PSRAM", + "label": "Has PSRAM", + "type": "boolean", + "depend": { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + }, + "value": false, + "setting": false + }, + { + "id": "memory_type", + "define": "board_build.arduino.memory_type", + "description": "Memory type", + "label": "Memory type", + "type": "select", + "value": "qio_qspi", + "depend": [ + { + "id": "targetmcu", + "value": [ + "esp32s3" + ] + }, + { + "id": "has_psram", + "value": [ + true + ] + } + ], + "setting": false, + "options": [ + { + "label": "Dio Flash / Octal PSRAM", + "value": "dio_opi" + }, + { + "label": "Quad", + "value": "qio_qspi" + }, + { + "label": "Quad Flash/ Octal PSRAM", + "value": "qio_opi" + }, + { + "label": "Octal", + "value": "opi_opi" + } + ] + } + ] + }, + { + "id": "communication", + "description": "Settings and protocols", + "label": "Serial Communications", + "setting": true, + "type": "group", + "value": [ + { + "id": "serialprotocol", + "define": "COMMUNICATION_PROTOCOL", + "description": "Communication protocol used by ESP3D to discuss with printer or TFT", + "label": "Serial Communication protocol", + "type": "select", + "value": "RAW_SERIAL", + "setting": true, + "options": [ + { + "label": "Raw serial", + "value": "RAW_SERIAL", + "help": "Basic serial protocol, without data change" + }, + { + "label": "MKS serial", + "value": "MKS_SERIAL", + "help": "This is a MakerBase communication protocol, used with MKS printers and TFT, it encapsulated data in a custom protocol" + } + ] + }, + { + "id": "serialport", + "define": "ESP_SERIAL_OUTPUT", + "description": "Which serial port ESP3D use to communicate with printer or TFT", + "label": "Serial port", + "type": "select", + "value": "USE_SERIAL_0", + "setting": true, + "isport": true + }, + { + "id": "serialbuffer", + "define": "SERIAL_RX_BUFFER_SIZE", + "description": "Maximum size of the serial buffer", + "label": "Serial buffer size", + "type": "select", + "value": "512", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "0 Byte", + "value": "0" + }, + { + "label": "128 Bytes", + "value": "128" + }, + { + "label": "256 Bytes", + "value": "256" + }, + { + "label": "512 Bytes", + "value": "512" + }, + { + "label": "1024 Bytes", + "value": "1024" + } + ] + }, + { + "id": "usbserial", + "define": "USB_SERIAL_FEATURE", + "label": "Use USB Serial", + "description": "Enable USB serial OTG port", + "type": "boolean", + "depend": { + "id": "targetmcu", + "value": [ + "esp32s3", + "esp32s2" + ] + }, + "value": false, + "disableiffalse": true, + "setting": true + }, + { + "id": "serialBridge", + "define": "SERIAL_BRIDGE_FEATURE", + "label": "Use serial bridge", + "description": "Enable serial bridge feature", + "type": "boolean", + "depend": { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + }, + "value": false, + "disableiffalse": true, + "hide": true, + "setting": false + }, + { + "id": "serialBridgeport", + "define": "ESP_SERIAL_BRIDGE_OUTPUT", + "description": "Which serial port serial bridge to output to", + "depend": { + "id": "serialBridge", + "value": [ + true + ] + }, + "label": "Serial bridge port", + "type": "select", + "value": "-1", + "setting": true, + "isport": true + } + ] + }, + { + "id": "targetFW", + "description": "Targeted firmware that ESP3D will communicate with", + "label": "Target firmware", + "setting": true, + "type": "group", + "value": [ + { + "id": "systemtype", + "define": "SYSTEM_TYPE", + "description": "The type of your system", + "label": "System type", + "type": "select", + "value": "Printer3D", + "setting": false, + "options": [ + { + "label": "CNC", + "value": "CNC", + "help": "CNC system used for milling/laser engraving/cutting" + }, + { + "label": "3D printer", + "value": "Printer3D", + "help": "3D printer system used with filaments" + }, + { + "label": "Sand Table", + "value": "SandTable", + "help": "System doing kinetic art, zen garden, sand table, etc." + } + ] + }, + { + "id": "defaultfw", + "define": "DEFAULT_FW", + "description": "The firmware that ESP3D will use by default", + "label": "Target firmware", + "type": "select", + "value": "UNKNOWN_FW", + "setting": true, + "options": [ + { + "label": "Undefined", + "value": "UNKNOWN_FW", + "help": "Undefined firmware" + }, + { + "label": "Marlin", + "value": "MARLIN", + "help": "Marlin 2.x firmware", + "depend": { + "id": "systemtype", + "value": [ + "Printer3D" + ] + } + }, + { + "label": "Repetier", + "value": "REPETIER", + "help": "Repetier 1.x/2.x firmware", + "depend": { + "id": "systemtype", + "value": [ + "Printer3D" + ] + } + }, + { + "label": "Smoothieware", + "value": "SMOOTHIEWARE", + "help": "Smoothieware 1.x/2.x firmware", + "depend": { + "id": "systemtype", + "value": [ + "Printer3D" + ] + } + }, + { + "label": "Grbl", + "value": "GRBL", + "help": "Grbl 1.1 firmware", + "depend": { + "id": "systemtype", + "value": [ + "CNC", + "SandTable" + ] + } + } + ] + } + ] + } +] diff --git a/src/configuration/tabs/filesystems.js b/src/configuration/tabs/filesystems.js new file mode 100644 index 0000000..25ed3e4 --- /dev/null +++ b/src/configuration/tabs/filesystems.js @@ -0,0 +1,3 @@ +import filesystemsConfiguration from "./filesystems.json" + +export default filesystemsConfiguration diff --git a/src/configuration/tabs/filesystems.json b/src/configuration/tabs/filesystems.json new file mode 100644 index 0000000..7320ae3 --- /dev/null +++ b/src/configuration/tabs/filesystems.json @@ -0,0 +1,466 @@ +[ + { + "id": "flash", + "description": "Filesystem on flash", + "label": "Flash filesystem", + "setting": true, + "type": "group", + "value": [ + { + "id": "flashFS", + "define": "FILESYSTEM_FEATURE", + "label": "File system type", + "description": "Type of file system used by ESP3D to store files", + "type": "select", + "value": "ESP_LITTLEFS_FILESYSTEM", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "LittleFS", + "value": "ESP_LITTLEFS_FILESYSTEM" + }, + { + "label": "FatFS", + "value": "ESP_FAT_FILESYSTEM", + "help": "Only on ESP32 with large flash", + "depend": { + "id": "targetmcu", + "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] + } + } + ] + }, + { + "id": "flashTimestamp", + "define": "FILESYSTEM_TIMESTAMP_FEATURE", + "label": "Enable date/time on files", + "description": "Set date/time on files using SNTP or last webui connection", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true + } + ] + }, + { + "id": "sd", + "description": "Filesystem on SD card", + "label": "SD filesystem", + "setting": true, + "type": "group", + "depend": { "id": "formfactor", "notvalue": ["esp01"] }, + "value": [ + { + "id": "sdconnection", + "define": "SD_DEVICE_CONNECTION", + "label": "SD card connection", + "description": "Does your system has SD card and how it is connected to your ESP3D", + "type": "select", + "value": "-1", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "None", + "value": "-1", + "help": "Your ESP does not have SDcard but your system may have one" + }, + { + "label": "Direct connection", + "value": "ESP_NOT_SHARED_SD", + "help": "Only your ESP board is connected to SDCard" + }, + { + "label": "Shared connection", + "value": "ESP_SHARED_SD", + "help": "Your system has SDCard and your ESP is connected to it using sharing solution" + } + ] + }, + { + "id": "sdfs", + "define": "SD_DEVICE", + "label": "SD card library", + "description": "Library used by SD card driver", + "type": "select", + "value": "ESP_SDFAT2", + "depend": { + "id": "sdconnection", + "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] + }, + "setting": true, + "options": [ + { + "label": "Native from core", + "value": "ESP_SD_NATIVE", + "help": "No additionnal library required" + }, + { + "label": "SDFat", + "value": "ESP_SDFAT2", + "help": "SDFat 2 library required" + }, + { + "label": "SDIO", + "value": "ESP_SDIO", + "help": "Used by SDMMC", + "depend": { + "id": "targetmcu", + "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] + } + } + ] + }, + { + "id": "sdio", + "define": "SDIO_BIT_MODE", + "label": "Sdio bit mode", + "description": "Mode used by SDIO library", + "type": "select", + "value": "SD_FOUR_BIT_MODE", + "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "setting": true, + "options": [ + { + "label": "1 bit", + "value": "SD_ONE_BIT_MODE" + }, + { + "label": "4 bits", + "value": "SD_FOUR_BIT_MODE" + } + ] + }, + { + "id": "sdtimestamp", + "define": "SD_TIMESTAMP_FEATURE", + "label": "Enable date/time on files", + "description": "Set date/time on files using SNTP or last webui connection", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true + } + ] + }, + { + "id": "sdpins", + "description": "SD card pins", + "label": "SD card pins", + "setting": true, + "type": "group", + "depend": { + "id": "sdconnection", + "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] + }, + "value": [ + { + "id": "detectsdpin", + "define": "ESP_SD_DETECT_PIN", + "label": "SD card detect pin", + "description": "The pin used to detect SD card", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "sdconnection", + "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "sddetectvalue", + "define": "ESP_SD_DETECT_VALUE", + "label": "Pin value", + "description": "State of SD card detect pin when card is present", + "type": "select", + "value": "0", + "depend": { "id": "detectsdpin", "notvalue": ["-1"] }, + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "High", + "value": "1" + }, + { + "label": "Low", + "value": "0" + } + ] + }, + { + "id": "shareflagsdpin", + "define": "ESP_FLAG_SHARED_SD_PIN", + "label": "SD shared flag pin", + "description": "The pin used to enable SD card for ESP board", + "type": "select", + "ispin": true, + "value": "-1", + "depend": { + "id": "sdconnection", + "value": ["ESP_SHARED_SD"] + }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "shareflagsdvalue", + "define": "ESP_FLAG_SHARED_SD_VALUE", + "label": "Pin value", + "description": "State of SD card share pin for ESP board", + "type": "select", + "value": "0", + "depend": { "id": "shareflagsdpin", "notvalue": ["-1"] }, + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "High", + "value": "1" + }, + { + "label": "Low", + "value": "0" + } + ] + }, + { + "id": "sdcspin", + "define": "ESP_SD_CS_PIN", + "label": "SD card CS pin", + "description": "The pin used to select SD card in SPI mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdmosipin", + "define": "ESP_SD_MOSI_PIN", + "label": "SD card Mosi pin", + "description": "The Mosi pin used by SD in SPI mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, + { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdmisopin", + "define": "ESP_SD_MISO_PIN", + "label": "SD card Miso pin", + "description": "The Miso pin used by SD in SPI mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, + { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdsckpin", + "define": "ESP_SD_SCK_PIN", + "label": "SD card Sck pin", + "description": "The Sck pin used by SD in SPI mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, + { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdioclkpin", + "define": "ESP_SDIO_CLK_PIN", + "label": "SDIO card clk pin", + "description": "The clk pin used by SD card in SDIO mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdiocmdpin", + "define": "ESP_SDIO_CMD_PIN", + "label": "SDIO card cmd pin", + "description": "The cmd pin used by SD card in SDIO mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdiod0pin", + "define": "ESP_SDIO_D0_PIN", + "label": "SDIO card data 0 pin", + "description": "The d0 pin used by SD card in SDIO mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdiod1pin", + "define": "ESP_SDIO_D1_PIN", + "label": "SDIO card data 1 pin", + "description": "The d1 pin used by SD card in SDIO mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { "id": "sdfs", "value": ["ESP_SDIO"] }, + { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdiod2pin", + "define": "ESP_SDIO_D2_PIN", + "label": "SDIO card data 2 pin", + "description": "The d2 pin used by SD card in SDIO mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { "id": "sdfs", "value": ["ESP_SDIO"] }, + { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "sdiod3pin", + "define": "ESP_SDIO_D3_PIN", + "label": "SDIO card data 3 pin", + "description": "The d3 pin used by SD card in SDIO mode", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { "id": "sdfs", "value": ["ESP_SDIO"] }, + { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } + ], + "setting": true, + "usedescforoptions": true + } + ] + }, + { + "id": "remote", + "description": "Remote filesystem access", + "label": "Remote access", + "setting": true, + "type": "group", + "value": [ + { + "id": "globalFS", + "define": "GLOBAL_FILESYSTEM_FEATURE", + "label": "Enable global filesystem", + "description": "Allows to access to all filesystems fromsame location", + "type": "boolean", + "value": false, + "depend": { "id": "sdconnection", "notvalue": ["-1"] }, + "disableiffalse": true, + "setting": true + }, + { + "id": "webdav", + "define": "WEBDAV_FEATURE", + "label": "WebDav access", + "description": "Use WebDav to access to your filesystem", + "type": "select", + "value": "-1", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "None", + "value": "-1" + }, + { + "label": "All filesystems", + "value": "FS_ROOT", + "depend": { "id": "globalFS", "value": [true] } + }, + { + "label": "ESP3D Flash", + "value": "FS_FLASH" + }, + { + "label": "ESP3D SD", + "value": "FS_SD", + "depend": { + "id": "sdconnection", + "notvalue": ["-1"] + } + } + ] + }, + { + "id": "ftp", + "define": "FTP_FEATURE", + "label": "FTP access", + "description": "Use FTP to access to your filesystem (1 connection only)", + "type": "select", + "value": "-1", + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "None", + "value": "-1" + }, + { + "label": "All filesystems", + "value": "FS_ROOT", + "depend": { "id": "globalFS", "value": [true] } + }, + { + "label": "ESP3D Flash", + "value": "FS_FLASH" + }, + { + "label": "ESP3D SD", + "value": "FS_SD", + "depend": { + "id": "sdconnection", + "notvalue": ["-1"] + } + } + ] + } + ] + } +] diff --git a/src/configuration/tabs/network.js b/src/configuration/tabs/network.js new file mode 100644 index 0000000..f29358b --- /dev/null +++ b/src/configuration/tabs/network.js @@ -0,0 +1,3 @@ +import networkConfiguration from "./network.json" + +export default networkConfiguration diff --git a/src/configuration/tabs/network.json b/src/configuration/tabs/network.json new file mode 100644 index 0000000..da80ce1 --- /dev/null +++ b/src/configuration/tabs/network.json @@ -0,0 +1,751 @@ +[ + { + "id": "radiomode", + "description": "The radio mode ESP3D communicate with the network", + "label": "Radio mode of ESP3D", + "setting": true, + "type": "group", + "value": [ + { + "id": "wifi", + "define": "WIFI_FEATURE", + "label": "Use WiFi ", + "description": "Enable wifi communications", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "ethernet", + "define": "ETH_FEATURE", + "label": "Use Ethernet ", + "description": "Enable ethernet communications", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true, + "depend": [ + { + "id": "formfactor", + "notvalue": [ + "esp01" + ] + }, + { + "id": "targetmcu", + "value": [ + "esp32", + "esp32s2", + "esp32s3", + "esp32c3" + ] + } + ] + }, + { + "id": "ethernetboard", + "define": "ESP3D_ETH_PHY_TYPE", + "label": "Ethernet chip", + "description": "Chip used by board", + "type": "select", + "value": "TYPE_ETH_PHY_LAN8720", + "depend": { + "id": "ethernet", + "value": "true" + }, + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "LAN8720", + "value": "TYPE_ETH_PHY_LAN8720" + }, + { + "label": "TLK110", + "value": "TYPE_ETH_PHY_TLK110" + }, + { + "label": "RTL8201", + "value": "TYPE_ETH_PHY_RTL8201" + }, + { + "label": "DP83848", + "value": "TYPE_ETH_PHY_DP83848" + }, + { + "label": "DM9051", + "value": "TYPE_ETH_PHY_DM9051" + }, + { + "label": "KSZ8041", + "value": "TYPE_ETH_PHY_KSZ8041" + }, + { + "label": "KSZ8081", + "value": "TYPE_ETH_PHY_KSZ8081" + }, + { + "label": "W5500 (SPI)", + "value": "TYPE_ETH_PHY_W5500" + } + ] + }, + { + "id": "ethernetclock", + "define": "ESP3D_ETH_CLK_MODE", + "label": "Ethernet board clock mode", + "description": "Mode used by clock", + "type": "select", + "value": "MODE_ETH_CLOCK_GPIO17_OUT", + "depend": [ + { + "id": "ethernetboard", + "notvalue": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "GPIO0 IN", + "value": "MODE_ETH_CLOCK_GPIO0_IN" + }, + { + "label": "GPIO0 OUT", + "value": "MODE_ETH_CLOCK_GPIO0_OUT" + }, + { + "label": "GPIO16 OUT", + "value": "MODE_ETH_CLOCK_GPIO16_OUT" + }, + { + "label": "GPIO17 OUT", + "value": "MODE_ETH_CLOCK_GPIO17_OUT" + } + ] + }, + { + "id": "ethernetaddress", + "define": "ESP3D_ETH_PHY_ADDR", + "label": "Ethernet board address", + "description": "Address used by clock", + "type": "select", + "value": "1", + "depend": { + "id": "ethernet", + "value": "true" + }, + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "0", + "value": "0" + }, + { + "label": "1", + "value": "1" + }, + { + "label": "2", + "value": "2" + }, + { + "label": "3", + "value": "3" + }, + { + "label": "4", + "value": "4" + }, + { + "label": "5", + "value": "5" + }, + { + "label": "6", + "value": "6" + }, + { + "label": "7", + "value": "7" + }, + { + "label": "8", + "value": "8" + }, + { + "label": "9", + "value": "9" + }, + { + "label": "10", + "value": "10" + }, + { + "label": "11", + "value": "11" + }, + { + "label": "12", + "value": "12" + }, + { + "label": "13", + "value": "13" + }, + { + "label": "14", + "value": "14" + }, + { + "label": "15", + "value": "15" + }, + { + "label": "16", + "value": "16" + }, + { + "label": "17", + "value": "17" + }, + { + "label": "18", + "value": "18" + }, + { + "label": "19", + "value": "19" + }, + { + "label": "20", + "value": "20" + }, + { + "label": "21", + "value": "21" + }, + { + "label": "22", + "value": "22" + }, + { + "label": "23", + "value": "23" + }, + { + "label": "24", + "value": "24" + }, + { + "label": "25", + "value": "25" + }, + { + "label": "26", + "value": "26" + }, + { + "label": "27", + "value": "27" + }, + { + "label": "28", + "value": "28" + }, + { + "label": "29", + "value": "29" + }, + { + "label": "30", + "value": "30" + }, + { + "label": "31", + "value": "31" + } + ] + }, + { + "id": "ethernetpowerpin", + "define": "ESP3D_ETH_PHY_POWER_PIN", + "label": "Ethernet power pin", + "description": "The pin used to power ethernet chip", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "notvalue": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetmdcpin", + "define": "ESP3D_ETH_PHY_MDC_PIN", + "label": "Ethernet mdc pin", + "description": "The pin used for MDC signal", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "notvalue": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetmdiopin", + "define": "ESP3D_ETH_PHY_MDIO_PIN", + "label": "Ethernet mdio pin", + "description": "The pin used for MDIO signal", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "notvalue": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetspiirqpin", + "define": "ETH_PHY_IRQ", + "label": "Ethernet irq pin", + "description": "The pin used for irq", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "value": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetspirstpin", + "define": "ETH_PHY_RST", + "label": "Ethernet reset pin", + "description": "The pin used for reset", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "value": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetspi", + "define": "", + "label": "Ethernet Dedicated SPI", + "description": "SPI Used by Ethernet", + "type": "select", + "value": "ETHERNET_SPI_USE_SPI2", + "depend": [ + { + "id": "ethernetboard", + "value": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true, + "options": [ + { + "label": "SPI", + "value": "ETHERNET_SPI_USE_SPI" + }, + { + "label": "SPI2", + "value": "ETHERNET_SPI_USE_SPI2" + } + ] + }, + { + "id": "ethernetspicspin", + "define": "ETH_PHY_CS", + "label": "Ethernet SPI CS pin", + "description": "The CS pin used by dedicated SPI ethernet", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "value": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetspimosipin", + "define": "ETH_SPI_MOSI", + "label": "Ethernet SPI MOSI pin", + "description": "The MOSI pin used by dedicated SPI ethernet", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "value": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetspimisopin", + "define": "ETH_SPI_MISO", + "label": "Ethernet SPI MISO pin", + "description": "The MISO pin used by dedicated SPI ethernet", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "value": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "ethernetspisckpin", + "define": "ETH_SPI_SCK", + "label": "Ethernet SPI SCK pin", + "description": "The SCK pin used by dedicated SPI ethernet", + "type": "select", + "ispin": true, + "usedefault": true, + "value": "-1", + "depend": [ + { + "id": "ethernetboard", + "value": "TYPE_ETH_PHY_W5500" + }, + { + "id": "ethernet", + "value": "true" + } + ], + "setting": true, + "usedescforoptions": true + }, + { + "id": "bluetooth", + "define": "BLUETOOTH_FEATURE", + "label": "Use Bluetooth ", + "description": "Enable serial bluetooth communications", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true, + "depend": { + "id": "targetmcu", + "value": [ + "esp32", + "esp32s2", + "esp32s3", + "esp32c3" + ] + } + } + ] + }, + { + "id": "channels", + "description": "The way ESP3D communicate", + "label": "Channels of ESP3D", + "setting": true, + "type": "group", + "value": [ + { + "id": "http", + "define": "HTTP_FEATURE", + "label": "Use webserver", + "description": "Enable http server", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "telnet", + "define": "TELNET_FEATURE", + "label": "Use telnet server", + "description": "Enable telnet light (raw tcp) communications", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "websocket", + "define": "WS_DATA_FEATURE", + "label": "Use Websocket server", + "description": "Enable websocket communications", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true + }, + { + "id": "notifications", + "define": "NOTIFICATION_FEATURE", + "label": "Enable notifications", + "description": "Allows to send notifications to the user", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "notificationonline", + "define": "NOTIFICATION_ESP_ONLINE", + "label": "Notification message when online", + "description": "The message that will be sent when the ESP is online", + "type": "text", + "needquote": true, + "value": "Hi, %ESP_NAME% is now online at %ESP_IP%", + "setting": true, + "depend": { + "id": "notifications", + "value": [ + true + ] + } + }, + { + "id": "notificationtitlemsg", + "define": "ESP_NOTIFICATION_TITLE", + "label": "Notification title message", + "description": "The title of notification", + "type": "text", + "needquote": true, + "value": "ESP3D Notification", + "setting": true, + "depend": { + "id": "notifications", + "value": [ + true + ] + } + } + ] + }, + { + "id": "discovery", + "description": "The discovery methods of ESP3D", + "label": "Discovery methods of ESP3D", + "setting": true, + "type": "group", + "value": [ + { + "id": "captive", + "define": "CAPTIVE_PORTAL_FEATURE", + "label": "Use captive portal ", + "description": "Enable captive portal in AP mode", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "mdns", + "define": "MDNS_FEATURE", + "label": "Use mDNS discovery", + "description": "This method need `bonjour` protocol on windows, or `avahi` on linux", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "ssdp", + "define": "SSDP_FEATURE", + "label": "Use Simple Service Discovery Protocol", + "description": "It is supported on Windows out of the box", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + } + ] + }, + { + "id": "customizationSSDP", + "description": "Customize your ESP3D", + "label": "SSDP Customization settings", + "setting": true, + "depend": { + "id": "ssdp", + "value": [ + true + ] + }, + "type": "group", + "value": [ + { + "id": "ssdpmodelename32", + "define": "ESP_MODEL_NAME", + "label": "Model name", + "description": "Modele name of device", + "type": "text", + "value": "ESP32", + "needquote": true, + "depend": { + "id": "targetmcu", + "value": [ + "esp32", + "esp32s2", + "esp32s3", + "esp32c3" + ] + }, + "setting": true + }, + { + "id": "ssdpmodelename8285", + "define": "ESP_MODEL_NAME", + "label": "Model name", + "description": "Modele name of device", + "type": "text", + "value": "ESP8285", + "needquote": true, + "depend": { + "id": "targetmcu", + "value": [ + "esp8285" + ] + }, + "setting": true + }, + { + "id": "ssdpmodelename8266", + "define": "ESP_MODEL_NAME", + "label": "Model name", + "description": "Modele name of device", + "type": "text", + "value": "ESP8266", + "needquote": true, + "depend": { + "id": "targetmcu", + "value": [ + "esp8266" + ] + }, + "setting": true + }, + { + "id": "ssdpmodelenamenumber", + "define": "ESP_MODEL_NUMBER", + "label": "Model number", + "description": "Modele number of device", + "type": "text", + "needquote": true, + "value": "ESP3D 3.0", + "setting": true + }, + { + "id": "ssdpmodeleurl", + "define": "ESP_MODEL_URL", + "label": "Model url", + "description": "Modele url of device", + "type": "text", + "needquote": true, + "value": "https://www.espressif.com/en/products/devkits", + "setting": true + }, + { + "id": "ssdpmodelemanufacturername", + "define": "ESP_MANUFACTURER_NAME", + "label": "Manufacturer name", + "description": "Manufacturer name of device", + "type": "text", + "needquote": true, + "value": "Espressif Systems", + "setting": true + }, + { + "id": "ssdpmodelemanufacturerurl", + "define": "ESP_MANUFACTURER_URL", + "label": "Manufacturer url", + "description": "Manufacturer url of device", + "type": "text", + "needquote": true, + "value": "https://www.espressif.com", + "setting": true + } + ] + } +] diff --git a/src/configuration/tabs/others.js b/src/configuration/tabs/others.js new file mode 100644 index 0000000..a6d6262 --- /dev/null +++ b/src/configuration/tabs/others.js @@ -0,0 +1,3 @@ +import othersConfiguration from "./others.json" + +export default othersConfiguration diff --git a/src/configuration/tabs/others.json b/src/configuration/tabs/others.json new file mode 100644 index 0000000..0bea1dd --- /dev/null +++ b/src/configuration/tabs/others.json @@ -0,0 +1,139 @@ +[ + { + "id": "additional", + "description": "Extra settings", + "label": "Additional features", + "setting": true, + "type": "group", + "value": [ + { + "id": "directpins", + "define": "DIRECT_PIN_FEATURE", + "label": "Enable direct control pin", + "description": "Controls pins using [ESP201]", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + } + ] + }, + { + "id": "scripting", + "description": "Scripting on ESP3D", + "label": "Scripting settings", + "setting": true, + "type": "group", + "value": [ + { + "id": "autorunscript", + "define": "AUTOSTART_SCRIPT_FEATURE", + "label": "Enable Autostart script", + "description": "Automaticaly launch script when ESP3D is started", + "setting": true, + "type": "select", + "value": "-1", + "usedescforoptions": true, + "options": [ + { + "label": "None", + "value": "-1" + }, + { + "label": "Commands", + "value": "\"script\"" + }, + { + "label": "File", + "value": "\"file\"" + } + ] + }, + { + "id": "autostartcommands", + "define": "ESP_AUTOSTART_SCRIPT", + "label": "Commands to run on startup", + "description": "Separate commands with ';'", + "depend": { + "id": "autorunscript", + "value": ["\"script\""] + }, + "type": "text", + "needquote": true, + "value": "M117 Mounting SD;M21", + "disableiffalse": true, + "setting": true + }, + { + "id": "autostartfile", + "define": "ESP_AUTOSTART_SCRIPT_FILE", + "label": "File to run on startup", + "description": "The file should be on ESP3D filesystem", + "depend": { "id": "autorunscript", "value": ["\"file\""] }, + "type": "text", + "needquote": true, + "value": "autorun.gco", + "disableiffalse": true, + "setting": true + }, + { + "id": "luainterpreter", + "define": "ESP_LUA_INTERPRETER_FEATURE", + "label": "Enable lua interpreter", + "description": "Allow to use lua interpreter on ESP3D", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true, + "depend": { "id": "targetmcu", "notvalue": ["esp8285", "esp8266"] } + }, + { + "id": "hook_ip", + "label": "Enable hook when getting IP", + "description": "Automaticaly launch script when get IP", + "setting": true, + "type": "boolean", + "value": false, + "disableiffalse": true + }, + { + "id": "get_ip_hook", + "define": "ESP_GOT_IP_HOOK", + "label": "Commands to run when got IP", + "description": "Separate commands with ';'", + "depend": { + "id": "hook_ip", + "value": [true] + }, + "type": "text", + "needquote": true, + "value": "[ESP212]IP:%ESP_IP%", + "setting": true + }, + { + "id": "hook_datetime", + "label": "Enable hook when getting date/time", + "description": "Automaticaly launch script when get date/time", + "setting": true, + "type": "boolean", + "value": false, + "disableiffalse": true + }, + { + "id": "get_datetime_hook", + "define": "ESP_GOT_DATE_TIME_HOOK", + "label": "Commands to run when got date/time", + "description": "Separate commands with ';'", + "depend": { + "id": "hook_datetime", + "value": [true] + }, + "type": "text", + "needquote": true, + "value": "[ESP212]DATE:%ESP_DATETIME%", + "disableiffalse": true, + "setting": true + } + ] + } +] diff --git a/src/configuration/tabs/security.js b/src/configuration/tabs/security.js new file mode 100644 index 0000000..f2638c6 --- /dev/null +++ b/src/configuration/tabs/security.js @@ -0,0 +1,3 @@ +import securityConfiguration from "./security.json" + +export default securityConfiguration diff --git a/src/configuration/tabs/security.json b/src/configuration/tabs/security.json new file mode 100644 index 0000000..f8eae5e --- /dev/null +++ b/src/configuration/tabs/security.json @@ -0,0 +1,41 @@ +[ + { + "id": "security", + "description": "How commands are allowed to be sent to ESP3D", + "label": "Levels of security", + "setting": true, + "type": "group", + "value": [ + { + "id": "serialcmdfeature", + "define": "SERIAL_COMMAND_FEATURE", + "label": "Enable serial commands", + "description": "Allow commands to be sent to ESP3D via serial port", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "corsfeature", + "define": "ESP_ACCESS_CONTROL_ALLOW_ORIGIN", + "label": "Enable cross origin access", + "description": "Allow to show the camera in web page different than device web server", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true + }, + { + "id": "authentication", + "define": "AUTHENTICATION_FEATURE", + "label": "Enable authentication", + "description": "Force usage of authentication for commands", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true + } + ] + } +] diff --git a/src/configuration/tabs/update.js b/src/configuration/tabs/update.js new file mode 100644 index 0000000..76cbb2f --- /dev/null +++ b/src/configuration/tabs/update.js @@ -0,0 +1,3 @@ +import updateConfiguration from "./update.json" + +export default updateConfiguration diff --git a/src/configuration/tabs/update.json b/src/configuration/tabs/update.json new file mode 100644 index 0000000..41e464c --- /dev/null +++ b/src/configuration/tabs/update.json @@ -0,0 +1,76 @@ +[ + { + "id": "reset", + "description": "Reset ESP3D settings", + "label": "Reset ESP3D", + "setting": true, + "type": "group", + "value": [ + { + "id": "resetfeature", + "define": "PIN_RESET_FEATURE", + "label": "Enable pin reset feature", + "description": "Use a pin to reset ESP3D settings", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": false, + "hide": true + }, + { + "id": "resetpin", + "define": "ESP3D_RESET_PIN", + "label": "Reset pin", + "description": "The pin used to reset ESP3D setting if set to low for more than 1 second at start", + "type": "select", + "ispin": true, + "header": "#define PIN_RESET_FEATURE\n", + "depend": { "id": "resetfeature", "value": [true] }, + "value": "-1", + "setting": true, + "usedescforoptions": true + } + ] + }, + { + "id": "update", + "description": "Update ESP3D firmware", + "label": "Update ESP3D", + "setting": true, + "depend": { "id": "targetflashsize", "notvalue": ["1", "2"] }, + "type": "group", + "value": [ + { + "id": "ota", + "define": "OTA_FEATURE", + "label": "Enable OTA", + "description": "Over The Air Update (OTA)", + "type": "boolean", + "value": false, + "disableiffalse": true, + "setting": true + }, + { + "id": "webupdate", + "define": "WEB_UPDATE_FEATURE", + "label": "Enable Web Update", + "description": "Update firmware using WebUI", + "type": "boolean", + "value": true, + "disableiffalse": true, + "setting": true + }, + { + "id": "sdupdate", + "define": "SD_UPDATE_FEATURE", + "label": "Enable SD card Update", + "description": "Update firmware and settings using file on SDCard", + "type": "boolean", + "depend": { "id": "sdconnection", "notvalue": ["-1"] }, + "value": true, + "disableiffalse": true, + "setting": true + } + ] + } +] diff --git a/src/contexts/DatasContext.js b/src/contexts/DatasContext.js index 53d108e..cd30a08 100644 --- a/src/contexts/DatasContext.js +++ b/src/contexts/DatasContext.js @@ -18,7 +18,7 @@ */ import { h, createContext } from "preact" import { useRef, useContext, useState } from "preact/hooks" -import defaultConfiguration from "../configuration.json" +import defaultConfiguration from "../configuration" /* * Local const diff --git a/src/pages/config/index.js b/src/pages/config/index.js index c35d9e0..4c2e44f 100644 --- a/src/pages/config/index.js +++ b/src/pages/config/index.js @@ -23,78 +23,28 @@ import { Router } from "../../components/Router" import { StepTab } from "../../tabs/step" import { GenerateTab } from "../../tabs/generate" import { TabBar } from "../../components/TabBar" -import { T } from "../../components/Translations" +import { configTabs } from "./steps" -const routes = { - FEATURES: { - component: , - path: "/config/features", - }, - NETWORK: { - component: ( - - ), - path: "/config/network", - }, - FILESYSTEMS: { - component: ( - - ), - path: "/config/filesystems", - }, - UPDATE: { - component: ( - - ), - path: "/config/update", - }, - DEVICES: { - component: ( - - ), - path: "/config/devices", - }, - SECURITY: { - component: ( - - ), - path: "/config/security", - }, - OTHERS: { - component: ( - - ), - path: "/config/others", - }, - GENERATE: { - component: , - path: "/config/generate", - }, -} +const routes = configTabs.reduce((acc, tab, index) => { + const previous = index > 0 ? configTabs[index - 1].id : undefined + const next = + index < configTabs.length - 1 ? configTabs[index + 1].id : undefined + + acc[tab.key] = { + component: + tab.type === "generate" ? ( + + ) : ( + + ), + path: tab.route, + } + return acc +}, {}) const Config = () => { return ( diff --git a/src/pages/config/steps.js b/src/pages/config/steps.js new file mode 100644 index 0000000..a6d54db --- /dev/null +++ b/src/pages/config/steps.js @@ -0,0 +1,88 @@ +import { h } from "preact" +import { AppLogo } from "../../components/Images/logo" +import { + Camera, + Download, + HardDrive, + Lock, + Tool, + Upload, + Wifi, +} from "preact-feather" + +const configTabs = [ + { + key: "FEATURES", + id: "featuresLink", + label: "Features", + icon: , + route: "/config/features", + section: "features", + type: "step", + }, + { + key: "NETWORK", + id: "networkLink", + label: "Network", + icon: , + route: "/config/network", + section: "network", + type: "step", + }, + { + key: "FILESYSTEMS", + id: "filesystemsLink", + label: "Filesystems", + icon: , + route: "/config/filesystems", + section: "filesystems", + type: "step", + }, + { + key: "UPDATE", + id: "updateLink", + label: "Update", + icon: , + route: "/config/update", + section: "update", + type: "step", + }, + { + key: "DEVICES", + id: "devicesLink", + label: "Devices", + icon: , + route: "/config/devices", + section: "devices", + type: "step", + }, + { + key: "SECURITY", + id: "securityLink", + label: "Security", + icon: , + route: "/config/security", + section: "security", + type: "step", + }, + { + key: "OTHERS", + id: "othersLink", + label: "Others", + icon: , + route: "/config/others", + section: "others", + type: "step", + }, + { + key: "GENERATE", + id: "generateLink", + label: "Download", + icon: , + route: "/config/generate", + section: "generate", + type: "generate", + }, +] + +export { configTabs } diff --git a/src/style/components/_navbar.scss b/src/style/components/_navbar.scss index d932561..d5c8079 100644 --- a/src/style/components/_navbar.scss +++ b/src/style/components/_navbar.scss @@ -18,7 +18,7 @@ li .active { } .tab-item { - pointer-events: none; + pointer-events: auto; } .tab { diff --git a/src/tabs/step/index.js b/src/tabs/step/index.js index aa7352b..840eb3b 100644 --- a/src/tabs/step/index.js +++ b/src/tabs/step/index.js @@ -29,7 +29,6 @@ import { T } from "../../components/Translations" import { useDatasContext, useDatasContextFn, - useUiContext, } from "../../contexts" import { ArrowLeft, ArrowRight } from "preact-feather" @@ -83,6 +82,32 @@ const getHelp = (item, value) => { return null } +const getOptionsList = (subelement, options) => { + if (subelement.ispin) { + if (subelement.usedefault) { + return JSON.parse( + JSON.stringify(mergeListOptions(pinsList, options)).replaceAll( + "None", + "Default" + ) + ) + } + return mergeListOptions(pinsList, options) + } + if (subelement.isport) { + if (subelement.usedefault) { + return JSON.parse( + JSON.stringify(mergeListOptions(portsList, options)).replaceAll( + "None", + "Default" + ) + ) + } + return mergeListOptions(portsList, options) + } + return options +} + const canshow = (depend, value, currentvalue, list = usedPinsList) => { if (value && value != "-1") { if (value == currentvalue && canshow(depend)) { @@ -151,6 +176,95 @@ const NavButtons = ({ previous, next }) => { ) } +const StepField = ({ subelement, current, generateValidation }) => { + if (!canshow(subelement.depend)) return null + if (typeof subelement.initial === "undefined") { + subelement.initial = subelement.value + } + const { label, initial, type, options, value, ...rest } = subelement + const optionsList = getOptionsList(subelement, options) + const filteredOptions = optionsList + ? optionsList.filter((opt) => { + return canshow( + opt.depend, + subelement.ispin || subelement.isport ? opt.value : null, + subelement.ispin || subelement.isport ? subelement.value : null, + subelement.ispin + ? usedPinsList + : subelement.isport + ? usedPortsList + : null + ) + }) + : null + if ( + filteredOptions && + filteredOptions.findIndex((op) => op.value == subelement.value) == -1 + ) { + if (subelement.ispin || subelement.isport) { + removeUsedElement( + subelement.value, + subelement.ispin ? usedPinsList : usedPortsList + ) + } + subelement.value = filteredOptions[0].value + } + const [help, setHelp] = useState( + subelement.options + ? getHelp(optionsList, subelement.value) + : subelement.description + ) + const [validation, setvalidation] = useState() + + useEffect(() => { + setHelp( + optionsList ? getHelp(optionsList, subelement.value) : subelement.description + ) + }, [subelement.value, current]) + + return ( + + { + if (!update) { + if (subelement.ispin || subelement.isport) { + removeUsedElement( + subelement.value, + subelement.ispin ? usedPinsList : usedPortsList + ) + addUsedElement( + val, + subelement.ispin ? usedPinsList : usedPortsList + ) + } + subelement.value = val + setHelp( + options + ? getHelp(optionsList, val) + : subelement.description + ) + } + setvalidation(generateValidation(subelement)) + }} + validation={validation} + /> + {help &&
    {help}
    } + {subelement.usedescforoptions && ( +
    {subelement.description}
    + )} +
    + + ) +} + const StepTab = ({ previous, current, next }) => { const { configuration } = useDatasContext() const generateValidation = (fieldData) => { @@ -178,226 +292,18 @@ const StepTab = ({ previous, current, next }) => { return ( - {element.value.map( - (subelement, subindex) => { - if (!canshow(subelement.depend)) - return null - if ( - typeof subelement.initial === - "undefined" - ) - subelement.initial = - subelement.value - const { - label, - initial, - type, - options, - value, - ...rest - } = subelement - const optionsList = - subelement.ispin - ? subelement.usedefault - ? JSON.parse( - JSON.stringify( - mergeListOptions( - pinsList, - options - ) - ).replaceAll( - "None", - "Default" - ) - ) - : mergeListOptions( - pinsList, - options - ) - : subelement.isport - ? subelement.usedefault - ? JSON.parse( - JSON.stringify( - mergeListOptions( - portsList, - options - ) - ).replaceAll( - "None", - "Default" - ) - ) - : mergeListOptions( - portsList, - options - ) - : options - const filteredOptions = - optionsList - ? optionsList.filter( - (opt) => { - return canshow( - opt.depend, - subelement.ispin || - subelement.isport - ? opt.value - : null, - subelement.ispin || - subelement.isport - ? subelement.value - : null, - subelement.ispin - ? usedPinsList - : subelement.isport - ? usedPortsList - : null - ) - } - ) - : null - if ( - filteredOptions && - filteredOptions.findIndex( - (op) => - op.value == - subelement.value - ) == -1 - ) { - if ( - subelement.ispin || - subelement.isport - ) { - removeUsedElement( - subelement.value, - subelement.ispin - ? usedPinsList - : usedPortsList - ) - } - subelement.value = - filteredOptions[0].value - } - const [help, setHelp] = - useState( - subelement.options - ? getHelp( - optionsList, - subelement.value - ) - : subelement.description - ) - const [ - validation, - setvalidation, - ] = useState() - //workaround to useState not always updating help at begining and use another one from another page... - useEffect(() => { - setHelp( - optionsList - ? getHelp( - optionsList, - subelement.value - ) - : subelement.description - ) - }, []) - useEffect(() => { - setHelp( - optionsList - ? getHelp( - optionsList, - subelement.value - ) - : subelement.description - ) - }, [subelement.value, current]) - return ( - - { - if (!update) { - if ( - subelement.ispin || - subelement.isport - ) { - removeUsedElement( - subelement.value, - subelement.ispin - ? usedPinsList - : usedPortsList - ) - addUsedElement( - val, - subelement.ispin - ? usedPinsList - : usedPortsList - ) - console.log( - subelement.ispin - ? usedPinsList - : usedPortsList - ) - } - subelement.value = - val - - setHelp( - options - ? getHelp( - optionsList, - val - ) - : subelement.description - ) - } - setvalidation( - generateValidation( - subelement - ) - ) - }} - validation={ - validation - } - /> - {help && ( -
    - {help} -
    - )} - {subelement.usedescforoptions && ( -
    - { - subelement.description - } -
    - )} -
    - - ) - } - )} + {element.value.map((subelement, subindex) => ( + + ))} ) } else { diff --git a/tools/validate_configuration.js b/tools/validate_configuration.js new file mode 100644 index 0000000..122ea76 --- /dev/null +++ b/tools/validate_configuration.js @@ -0,0 +1,126 @@ +/** + * Validates src/configuration/tabs/*.json + * - unique field ids across all tabs + * - depend.id references point to existing ids + * - JSON syntax + * + * Usage: node tools/validate_configuration.js + */ + +const fs = require("fs") +const path = require("path") + +const tabsDir = path.join(__dirname, "..", "src", "configuration", "tabs") +const errors = [] +const warnings = [] + +const collectIds = (node, file, locations, ids) => { + if (!node || typeof node !== "object") return + if (Array.isArray(node)) { + node.forEach((item, index) => + collectIds(item, file, `${locations}[${index}]`, ids) + ) + return + } + if (typeof node.id === "string") { + const entry = { file, location: locations } + if (!ids.has(node.id)) ids.set(node.id, []) + ids.get(node.id).push(entry) + } + if (node.value) collectIds(node.value, file, `${locations}.value`, ids) + if (node.options) collectIds(node.options, file, `${locations}.options`, ids) +} + +const collectDependIds = (node, file, locations, depends) => { + if (!node || typeof node !== "object") return + if (Array.isArray(node)) { + node.forEach((item, index) => + collectDependIds(item, file, `${locations}[${index}]`, depends) + ) + return + } + if (node.depend) { + const deps = Array.isArray(node.depend) ? node.depend : [node.depend] + deps.forEach((dep, index) => { + if (dep && dep.id) { + depends.push({ + refId: dep.id, + file, + location: `${locations}.depend${Array.isArray(node.depend) ? `[${index}]` : ""}`, + }) + } + }) + } + if (node.value) collectDependIds(node.value, file, `${locations}.value`, depends) + if (node.options) collectDependIds(node.options, file, `${locations}.options`, depends) +} + +const jsonFiles = fs + .readdirSync(tabsDir) + .filter((name) => name.endsWith(".json")) + .sort() + +const ids = new Map() +const depends = [] + +for (const file of jsonFiles) { + const filePath = path.join(tabsDir, file) + let data + try { + data = JSON.parse(fs.readFileSync(filePath, "utf8")) + } catch (e) { + errors.push(`${file}: invalid JSON — ${e.message}`) + continue + } + if (!Array.isArray(data)) { + errors.push(`${file}: root must be a JSON array of groups`) + continue + } + collectIds(data, file, file, ids) + collectDependIds(data, file, file, depends) +} + +for (const [id, locations] of ids) { + if (locations.length > 1) { + const where = locations.map((l) => `${l.file} (${l.location})`).join("; ") + errors.push(`duplicate id "${id}": ${where}`) + } +} + +const idSet = new Set(ids.keys()) +for (const dep of depends) { + if (!idSet.has(dep.refId)) { + errors.push( + `unknown depend.id "${dep.refId}" in ${dep.file} (${dep.location})` + ) + } +} + +// Sections referenced by UI should have a matching JSON file +const stepsPath = path.join(__dirname, "..", "src", "pages", "config", "steps.js") +const stepsSource = fs.readFileSync(stepsPath, "utf8") +const sectionMatches = [...stepsSource.matchAll(/section:\s*"([^"]+)"/g)].map( + (m) => m[1] +) +for (const section of sectionMatches) { + if (section === "generate") continue + const expected = `${section}.json` + if (!jsonFiles.includes(expected) && section !== "default") { + warnings.push(`configTabs section "${section}" has no ${expected}`) + } +} + +if (warnings.length) { + console.warn("Warnings:") + warnings.forEach((w) => console.warn(` - ${w}`)) +} + +if (errors.length) { + console.error("Configuration validation failed:") + errors.forEach((e) => console.error(` - ${e}`)) + process.exit(1) +} + +console.log( + `Configuration OK (${jsonFiles.length} tab files, ${idSet.size} unique field ids, ${depends.length} depend rules checked)` +) From 485771aede7c993637127c46a119254e2e2de403 Mon Sep 17 00:00:00 2001 From: Luc <8822552+luc-github@users.noreply.github.com> Date: Tue, 2 Jun 2026 21:22:04 +0800 Subject: [PATCH 2/4] Display default pins and do immediate update of availablble GPIO Block configuration files download if any GPIO conflict --- .github/workflows/ci.yml | 20 ++ API.md | 21 +- build/index.html | 2 +- src/components/Router/index.js | 37 ++-- src/configuration/mcuPinDefaults.js | 79 +++++++ src/configuration/pinConflicts.js | 83 ++++++++ src/configuration/reservedResources.js | 72 +++++++ src/configuration/validateField.js | 75 +++++++ src/configuration/visibility.js | 42 ++++ src/tabs/generate/index.js | 278 ++++++++++++------------- src/tabs/step/index.js | 138 ++++++------ src/translations/en.json | 9 +- tools/validate_configuration.js | 52 ++++- 13 files changed, 654 insertions(+), 254 deletions(-) create mode 100644 .github/workflows/ci.yml create mode 100644 src/configuration/mcuPinDefaults.js create mode 100644 src/configuration/pinConflicts.js create mode 100644 src/configuration/reservedResources.js create mode 100644 src/configuration/validateField.js create mode 100644 src/configuration/visibility.js diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml new file mode 100644 index 0000000..07f92c8 --- /dev/null +++ b/.github/workflows/ci.yml @@ -0,0 +1,20 @@ +name: CI + +on: + push: + branches: [main, master] + pull_request: + branches: [main, master] + +jobs: + build: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: "20" + cache: npm + - run: npm ci + - run: npm run validate:config + - run: npm run build diff --git a/API.md b/API.md index 288d1f1..9e55385 100644 --- a/API.md +++ b/API.md @@ -10,6 +10,11 @@ Configuration is split per tab under `src/configuration/` (one JSON file per wiz src/ configuration/ index.js # Aggregates all tab sections into one object + visibility.js # Shared depend / visibility rules (UI + generate) + mcuPinDefaults.js # MCU default GPIO for SPI when pin = Default (-1) + pinConflicts.js # Resolve effective GPIO + conflict audit + validateField.js # Per-field validation in step tabs + reservedResources.js # Cross-tab pin/port reservation lists tabs/ features.json # Tab data (array of groups) features.js # import ... from "./features.json" @@ -125,7 +130,17 @@ For string defines in `depend.value`, use the same quoting as in `value` (e.g. ` ### Pin and port allocation -When `ispin` or `isport` is set, the step UI tracks used pins/ports globally so the same resource is not selected twice. Lists come from: +When `ispin` or `isport` is set, the step UI tracks used pins/ports **across all tabs** (not only the current step). Lists come from: + +- `reservedResources.js` — explicit GPIO values plus **resolved** defaults (see below) +- **Download** tab — `auditPinConflicts()` lists any GPIO still shared after resolution; while conflicts remain, **configuration.h** / **platformio.ini** downloads and file previews are hidden + +**Default pins (`value: "-1"` + `usedefault: true`)** +For shared SPI signals (MOSI/MISO/SCK/CS), `-1` is resolved to the MCU defaults in `mcuPinDefaults.js` (same numbers as `platformio.ini` / TFT build flags). In the UI, the default option is shown as `-1 (5)` (GPIO number for the selected MCU). Example: on ESP32, SD CS Default and TFT CS Default both resolve to GPIO 5 → reported as a conflict. + +Field ids mapped to defaults are listed in `defaultPinRoles` inside `mcuPinDefaults.js`. Other `-1` pins (optional / board-specific) are not resolved and do not participate in this check. + +Pin/port pick lists: - `src/tabs/step/pins.json` - `src/tabs/step/ports.json` @@ -152,3 +167,7 @@ Settings in the `default` section are included in `configuration.h` even without 2. Set `define`, `type`, `value`, `setting`, and `depend` if needed. 3. Rebuild and test the tab in the UI and on Download (preview + download). 4. Confirm the matching define exists in [ESP3D](https://github.com/luc-github/ESP3D) firmware. + +## Planned (not implemented) + +- **Immutable configuration state** — field values are still updated in place on the loaded JSON (`subelement.value = …`). A future refactor would use immutable updates for clearer React updates, undo, and tests. diff --git a/build/index.html b/build/index.html index f63189a..75d1fd8 100644 --- a/build/index.html +++ b/build/index.html @@ -1 +1 @@ -ESP3D-Configurator \ No newline at end of file +ESP3D-Configurator \ No newline at end of file diff --git a/src/components/Router/index.js b/src/components/Router/index.js index 4ef6646..8c4141b 100644 --- a/src/components/Router/index.js +++ b/src/components/Router/index.js @@ -146,29 +146,24 @@ const Link = ({ ...rest }) => { const { activeRoute } = useRouterContext() - const [mergedClassName, setMergedClassName] = useState() - - useEffect(() => { - const route = window.location.hash.slice(1).toLowerCase() - if ( - (activeRoute == "/config" && href == route) || - (route.startsWith("/config") && href == "/config") - ) { - setMergedClassName(`${className} ${activeClassName}`) - } else - setMergedClassName( - activeRoute === href - ? `${className} ${activeClassName}` - : className - ) - }, [activeRoute]) + const route = + typeof window !== "undefined" + ? window.location.hash.slice(1).toLowerCase() + : "" + let mergedClassName = className + if ( + (activeRoute == "/config" && href == route) || + (route.startsWith("/config") && href == "/config") + ) { + mergedClassName = `${className} ${activeClassName}` + } else if (activeRoute === href) { + mergedClassName = `${className} ${activeClassName}` + } return ( - mergedClassName && ( - - {children} - - ) + + {children} + ) } diff --git a/src/configuration/mcuPinDefaults.js b/src/configuration/mcuPinDefaults.js new file mode 100644 index 0000000..ea18d07 --- /dev/null +++ b/src/configuration/mcuPinDefaults.js @@ -0,0 +1,79 @@ +/** + * MCU default GPIO numbers for SPI signals when a field uses value "-1" + usedefault. + * Must stay aligned with src/tabs/generate/index.js sections.*.default*. + */ +const mcuPinDefaults = { + esp32: { + defaultMosi: 23, + defaultMiso: 19, + defaultSck: 18, + defaultCs: 5, + defaultSda: 21, + defaultScl: 22, + }, + esp32s2: { + defaultMosi: 35, + defaultMiso: 37, + defaultSck: 36, + defaultCs: 34, + defaultSda: 8, + defaultScl: 9, + }, + esp32s3: { + defaultMosi: 11, + defaultMiso: 13, + defaultSck: 12, + defaultCs: 10, + defaultSda: 8, + defaultScl: 9, + }, + esp32c3: { + defaultMosi: 6, + defaultMiso: 5, + defaultSck: 4, + defaultCs: 7, + defaultSda: 8, + defaultScl: 9, + }, + esp32c6: { + defaultMosi: 6, + defaultMiso: 5, + defaultSck: 4, + defaultCs: 7, + defaultSda: 8, + defaultScl: 9, + }, + esp8266: { + defaultMosi: 13, + defaultMiso: 12, + defaultSck: 14, + defaultCs: 15, + defaultSda: 4, + defaultScl: 5, + }, + esp8285: { + defaultMosi: 13, + defaultMiso: 12, + defaultSck: 14, + defaultCs: 15, + defaultSda: 4, + defaultScl: 5, + }, +} + +/** Field id → key in mcuPinDefaults[mcu] */ +const defaultPinRoles = { + sdmosipin: "defaultMosi", + tftMOSIpin: "defaultMosi", + ethernetspimosipin: "defaultMosi", + sdmisopin: "defaultMiso", + ethernetspimisopin: "defaultMiso", + sdsckpin: "defaultSck", + tftSCKpin: "defaultSck", + ethernetspisckpin: "defaultSck", + sdcspin: "defaultCs", + tftCSpin: "defaultCs", + ethernetspicspin: "defaultCs", +} + +export { mcuPinDefaults, defaultPinRoles } diff --git a/src/configuration/pinConflicts.js b/src/configuration/pinConflicts.js new file mode 100644 index 0000000..a164f51 --- /dev/null +++ b/src/configuration/pinConflicts.js @@ -0,0 +1,83 @@ +import { canShowField } from "./visibility" +import { mcuPinDefaults, defaultPinRoles } from "./mcuPinDefaults" + +const forEachField = (configuration, getValueId, visitor) => { + if (!configuration) return + Object.keys(configuration).forEach((section) => { + const groups = configuration[section] + if (!Array.isArray(groups)) return + groups.forEach((group) => { + if (group.type !== "group" || !Array.isArray(group.value)) return + if (!canShowField(group.depend, getValueId)) return + group.value.forEach((field) => { + if (!canShowField(field.depend, getValueId)) return + visitor(field, section) + }) + }) + }) +} + +const resolveEffectivePin = (field, getValueId) => { + if (!field.ispin) return null + const raw = field.value + if (raw != "-1") return String(raw) + if (!field.usedefault) return null + const role = defaultPinRoles[field.id] + if (!role) return null + const mcu = getValueId("targetmcu") + const defs = mcuPinDefaults[mcu] + if (!defs || defs[role] === undefined) return null + return String(defs[role]) +} + +const describePinAssignment = (field, effective) => { + if (field.value == "-1" && field.usedefault && effective) { + return `Default (GPIO ${effective})` + } + if (field.value == "-1") { + return "None" + } + return `GPIO ${field.value}` +} + +const collectPinUsages = (configuration, getValueId) => { + const usages = [] + forEachField(configuration, getValueId, (field, section) => { + const effective = resolveEffectivePin(field, getValueId) + if (!effective) return + usages.push({ + section, + id: field.id, + label: field.label, + raw: field.value, + effective, + description: describePinAssignment(field, effective), + }) + }) + return usages +} + +const auditPinConflicts = (configuration, getValueId) => { + const usages = collectPinUsages(configuration, getValueId) + const byGpio = new Map() + usages.forEach((usage) => { + if (!byGpio.has(usage.effective)) { + byGpio.set(usage.effective, []) + } + byGpio.get(usage.effective).push(usage) + }) + const conflicts = [] + byGpio.forEach((entries, gpio) => { + if (entries.length > 1) { + conflicts.push({ gpio, entries }) + } + }) + return { usages, conflicts } +} + +export { + resolveEffectivePin, + collectPinUsages, + auditPinConflicts, + describePinAssignment, +} diff --git a/src/configuration/reservedResources.js b/src/configuration/reservedResources.js new file mode 100644 index 0000000..7aaa8e8 --- /dev/null +++ b/src/configuration/reservedResources.js @@ -0,0 +1,72 @@ +import { canShowField } from "./visibility" +import { resolveEffectivePin } from "./pinConflicts" + +const forEachField = (configuration, getValueId, visitor) => { + if (!configuration) return + Object.keys(configuration).forEach((section) => { + const groups = configuration[section] + if (!Array.isArray(groups)) return + groups.forEach((group) => { + if (group.type !== "group" || !Array.isArray(group.value)) return + if (!canShowField(group.depend, getValueId)) return + group.value.forEach((field) => { + if (!canShowField(field.depend, getValueId)) return + visitor(field) + }) + }) + }) +} + +const findDuplicateValues = (values) => { + const counts = new Map() + values.forEach((value) => { + counts.set(value, (counts.get(value) || 0) + 1) + }) + return [...counts.entries()] + .filter(([, count]) => count > 1) + .map(([value]) => value) +} + +const buildReservedLists = (configuration, getValueId, excludeFieldId) => { + const pins = [] + const ports = [] + forEachField(configuration, getValueId, (field) => { + if (excludeFieldId && field.id === excludeFieldId) return + if (field.ispin) { + const effective = resolveEffectivePin(field, getValueId) + if (effective) { + pins.push(effective) + } else if (field.value != "-1") { + pins.push(String(field.value)) + } + } + if (field.isport && field.value != "-1") { + ports.push(String(field.value)) + } + }) + if (ports.length === 0) { + ports.push("USE_SERIAL_0") + } + return { pins, ports } +} + +const syncReservedResources = ( + configuration, + getValueId, + usedPinsList, + usedPortsList +) => { + const { pins, ports } = buildReservedLists(configuration, getValueId) + usedPinsList.current = pins + usedPortsList.current = ports + return { + duplicatePins: findDuplicateValues(pins), + duplicatePorts: findDuplicateValues(ports), + } +} + +export { + buildReservedLists, + syncReservedResources, + findDuplicateValues, +} diff --git a/src/configuration/validateField.js b/src/configuration/validateField.js new file mode 100644 index 0000000..681322a --- /dev/null +++ b/src/configuration/validateField.js @@ -0,0 +1,75 @@ +import { canShowField } from "./visibility" +import { findDuplicateValues, buildReservedLists } from "./reservedResources" +import { auditPinConflicts, resolveEffectivePin } from "./pinConflicts" + +const validateField = (field, getValueId, configuration) => { + const validation = { + message: "", + valid: true, + modified: false, + } + if (!field || !canShowField(field.depend, getValueId)) { + return validation + } + if (!field.setting) { + return validation + } + if (field.type === "text" || field.type === "number") { + const value = field.value + if (value === "" || value === undefined || value === null) { + return { + message: "Value is required", + valid: false, + modified: true, + } + } + } + if ( + field.id && + (field.id.toLowerCase().includes("i2c") || + field.define === "DISPLAY_I2C_ADDR" || + field.define === "SENSOR_ADDR") + ) { + const value = String(field.value || "").trim() + if (value && !/^0x[0-9a-fA-F]+$/.test(value)) { + return { + message: "Use hex format (e.g. 0x3c)", + valid: false, + modified: true, + } + } + } + if (configuration) { + if (field.ispin) { + const effective = resolveEffectivePin(field, getValueId) + if (effective) { + const { conflicts } = auditPinConflicts(configuration, getValueId) + const conflict = conflicts.find((c) => c.gpio === effective) + if (conflict) { + const others = conflict.entries.filter((e) => e.id !== field.id) + if (others.length) { + return { + message: `GPIO ${effective} conflicts with ${others[0].label} (${others[0].description})`, + valid: false, + modified: true, + } + } + } + } + } + if (field.isport && field.value != "-1") { + const { ports } = buildReservedLists(configuration, getValueId, field.id) + const duplicates = findDuplicateValues(ports) + if (duplicates.includes(String(field.value))) { + return { + message: "This serial port is already used", + valid: false, + modified: true, + } + } + } + } + return validation +} + +export { validateField } diff --git a/src/configuration/visibility.js b/src/configuration/visibility.js new file mode 100644 index 0000000..80fe8b2 --- /dev/null +++ b/src/configuration/visibility.js @@ -0,0 +1,42 @@ +const matchDependRule = (rule, getValueId) => { + const val = getValueId(rule.id) + if (rule.value !== undefined) { + return rule.value.includes(val) + } + if (rule.notvalue !== undefined) { + return !rule.notvalue.includes(val) + } + return true +} + +const evaluateDepend = (depend, getValueId) => { + if (!depend) return true + if (Array.isArray(depend)) { + return depend.every((rule) => rule && matchDependRule(rule, getValueId)) + } + return matchDependRule(depend, getValueId) +} + +/** + * Whether a group/field/option should be visible. + * @param {object|object[]|undefined} depend + * @param {function(string): *} getValueId + * @param {{ resourceValue?: string, currentResourceValue?: string, reservedList?: { current: string[] } }} [options] + */ +const canShowField = (depend, getValueId, options = {}) => { + const { resourceValue, currentResourceValue, reservedList } = options + if (resourceValue && resourceValue != "-1" && reservedList) { + if ( + resourceValue == currentResourceValue && + evaluateDepend(depend, getValueId) + ) { + return true + } + if (reservedList.current.includes(resourceValue)) { + return false + } + } + return evaluateDepend(depend, getValueId) +} + +export { canShowField, evaluateDepend } diff --git a/src/tabs/generate/index.js b/src/tabs/generate/index.js index a1af8d8..e7dfdd7 100644 --- a/src/tabs/generate/index.js +++ b/src/tabs/generate/index.js @@ -33,6 +33,9 @@ import footer from "./footer" import piofooter from "./piofooter" import { Version } from "../../components/App/version" import { useEffect, useState } from "preact/hooks" +import { canShowField } from "../../configuration/visibility" +import { auditPinConflicts } from "../../configuration/pinConflicts" +import { mcuPinDefaults } from "../../configuration/mcuPinDefaults" const pioIcon = ( { : "fatfs" const resetmethod = useDatasContextFn.getValueId("resetmethod") const memory_type = useDatasContextFn.getValueId("memory_type") - console.log("filesystem", filesystem) let res = "" const envName = `${target}_${targetsize}MB${ cameraName == "-1" ? "" : "_cam" @@ -529,39 +496,10 @@ const getHelp = (item, value) => { return null } -const canshow = (depend, pinvalue, currentvalue) => { - if (pinvalue && pinvalue != "-1") { - if (pinvalue == currentvalue && canshow(depend)) return true - if (usedPinsList.current.includes(pinvalue)) return false - } - if (depend) { - if (Array.isArray(depend)) { - const res = depend.reduce((acc, curdep) => { - if (!acc) return acc - const val = useDatasContextFn.getValueId(curdep.id) - if (curdep.value) { - return curdep.value.includes(val) - } - if (curdep.notvalue) { - return !curdep.notvalue.includes(val) - } - }, true) - return res - } else { - const val = useDatasContextFn.getValueId(depend.id) - if (depend.value) { - return depend.value.includes(val) - } - if (depend.notvalue) { - return !depend.notvalue.includes(val) - } - } - } - return true -} +const canshow = (depend) => + canShowField(depend, useDatasContextFn.getValueId) const convertToText = (data) => { - //console.log(data) let config = "" return Object.keys(data).reduce((acc, item) => { return data[item].reduce((acc2, item2) => { @@ -631,15 +569,9 @@ const convertToText = (data) => { }\n` ) } else { - console.log( - "unknown type", - element.type, - " for ", - element - ) return ( acc3 + - `\n// Unknow parameter: ${element.label}\n` + + `\n// Unknown parameter: ${element.label}\n` + `// ${element.description}\n` ) } @@ -659,8 +591,6 @@ const convertToText = (data) => { : acc2 + sectionFormated(item2.label, item2.description) + content - } else { - console.log("Group definition is missing for " + item2.label) } }, acc) }, config) @@ -691,10 +621,11 @@ const exportFile = (filecontent, filename) => { let showconfig = false let showini = false -const NavButtons = ({ previous, next }) => { +const NavButtons = ({ previous, next, pinConflicts = [] }) => { const { configuration } = useDatasContext() + const downloadBlocked = pinConflicts.length > 0 return ( -
    +
    {previous && ( { m2 icon={} label={T("Download configuration.h")} + disable={downloadBlocked} + disabled={downloadBlocked} onclick={() => { + if (downloadBlocked) return exportFile( configurationFile(configuration.current), "configuration.h" @@ -722,7 +656,10 @@ const NavButtons = ({ previous, next }) => { m2 icon={pioIcon} label={T("Download platformio.ini")} + disable={downloadBlocked} + disabled={downloadBlocked} onclick={() => { + if (downloadBlocked) return exportFile(pioFile(), "platformio.ini") }} /> @@ -764,79 +701,124 @@ const NavButtons = ({ previous, next }) => { ) } +const PinConflictBanner = ({ conflicts }) => { + if (!conflicts.length) return null + return ( +
    + {T("Pin conflicts block download")} +

    + {T( + "Firmware downloads are disabled until each GPIO is used by only one visible feature. Default (-1) pins are resolved for your MCU." + )} +

    +
      + {conflicts.map(({ gpio, entries }) => ( +
    • + GPIO {gpio}:{" "} + {entries + .map((e) => `${e.label} (${e.description})`) + .join("; ")} +
    • + ))} +
    +
    + ) +} + const GenerateTab = ({ previous }) => { const { configuration } = useDatasContext() const [showContent, setshowContent] = useState(showconfig) const [showIniContent, setshowIniContent] = useState(showini) + const pinConflicts = auditPinConflicts( + configuration.current, + useDatasContextFn.getValueId + ).conflicts + const downloadBlocked = pinConflicts.length > 0 return (
    - -
    - - - {showContent && ( -
    - -
    -                                {configurationFile(configuration.current)}
    -                            
    -
    + + + {!downloadBlocked && ( + +
    + + + {showContent && ( +
    + +
    +                                        {configurationFile(
    +                                            configuration.current
    +                                        )}
    +                                    
    +
    +
    + )}
    - )} -
    -
    - - - {showIniContent && ( -
    - -
    {pioFile()}
    -
    +
    + + + {showIniContent && ( +
    + +
    {pioFile()}
    +
    +
    + )} +
    - )} -
    -
    - {showContent && } + {showContent && ( + + )} + + )}
    ) diff --git a/src/tabs/step/index.js b/src/tabs/step/index.js index 840eb3b..f6813c5 100644 --- a/src/tabs/step/index.js +++ b/src/tabs/step/index.js @@ -30,26 +30,15 @@ import { useDatasContext, useDatasContextFn, } from "../../contexts" +import { canShowField } from "../../configuration/visibility" +import { validateField } from "../../configuration/validateField" +import { buildReservedLists } from "../../configuration/reservedResources" +import { resolveEffectivePin } from "../../configuration/pinConflicts" import { ArrowLeft, ArrowRight } from "preact-feather" import pinsList from "./pins.json" import portsList from "./ports.json" -const usedPinsList = {} -usedPinsList.current = [] - -const usedPortsList = {} -usedPortsList.current = ["USE_SERIAL_0"] - -const removeUsedElement = (el, list) => { - if (el == "-1") return - list.current = list.current.filter((element) => element != el) -} -const addUsedElement = (el, list) => { - if (el == "-1") return - list.current.push(el) -} - const mergeListOptions = (list, options) => { if (list) { const newList = JSON.parse(JSON.stringify(list)) @@ -108,41 +97,27 @@ const getOptionsList = (subelement, options) => { return options } -const canshow = (depend, value, currentvalue, list = usedPinsList) => { - if (value && value != "-1") { - if (value == currentvalue && canshow(depend)) { - return true - } - if (list.current.includes(value)) { - return false - } - } - if (depend) { - if (Array.isArray(depend)) { - const res = depend.reduce((acc, curdep) => { - if (!acc) return acc - const val = useDatasContextFn.getValueId(curdep.id) - if (curdep.value) { - return curdep.value.includes(val) - } - if (curdep.notvalue) { - return !curdep.notvalue.includes(val) - } - }, true) - return res - } else { - const val = useDatasContextFn.getValueId(depend.id) - if (depend.value) { - return depend.value.includes(val) - } - if (depend.notvalue) { - return !depend.notvalue.includes(val) - } - } +const annotateDefaultPinLabels = (subelement, optionsList) => { + if (!optionsList || !subelement.ispin || !subelement.usedefault) { + return optionsList } - return true + const effective = resolveEffectivePin( + { ...subelement, value: "-1" }, + useDatasContextFn.getValueId + ) + if (!effective) return optionsList + return optionsList.map((opt) => + opt.value == "-1" ? { ...opt, label: `-1 (${effective})` } : opt + ) } +const canshow = (depend, value, currentvalue, list) => + canShowField(depend, useDatasContextFn.getValueId, { + resourceValue: value, + currentResourceValue: currentvalue, + reservedList: list, + }) + const NavButtons = ({ previous, next }) => { return (
    @@ -176,24 +151,36 @@ const NavButtons = ({ previous, next }) => { ) } -const StepField = ({ subelement, current, generateValidation }) => { +const StepField = ({ + subelement, + current, + configuration, + generateValidation, + onFieldChange, +}) => { if (!canshow(subelement.depend)) return null + const reserved = buildReservedLists( + configuration, + useDatasContextFn.getValueId, + subelement.id + ) + const pinList = { current: reserved.pins } + const portList = { current: reserved.ports } if (typeof subelement.initial === "undefined") { subelement.initial = subelement.value } const { label, initial, type, options, value, ...rest } = subelement - const optionsList = getOptionsList(subelement, options) + const optionsList = annotateDefaultPinLabels( + subelement, + getOptionsList(subelement, options) + ) const filteredOptions = optionsList ? optionsList.filter((opt) => { return canshow( opt.depend, subelement.ispin || subelement.isport ? opt.value : null, subelement.ispin || subelement.isport ? subelement.value : null, - subelement.ispin - ? usedPinsList - : subelement.isport - ? usedPortsList - : null + subelement.ispin ? pinList : subelement.isport ? portList : null ) }) : null @@ -201,13 +188,8 @@ const StepField = ({ subelement, current, generateValidation }) => { filteredOptions && filteredOptions.findIndex((op) => op.value == subelement.value) == -1 ) { - if (subelement.ispin || subelement.isport) { - removeUsedElement( - subelement.value, - subelement.ispin ? usedPinsList : usedPortsList - ) - } subelement.value = filteredOptions[0].value + onFieldChange() } const [help, setHelp] = useState( subelement.options @@ -235,17 +217,8 @@ const StepField = ({ subelement, current, generateValidation }) => { validationfn={generateValidation} setValue={(val, update = false) => { if (!update) { - if (subelement.ispin || subelement.isport) { - removeUsedElement( - subelement.value, - subelement.ispin ? usedPinsList : usedPortsList - ) - addUsedElement( - val, - subelement.ispin ? usedPinsList : usedPortsList - ) - } subelement.value = val + onFieldChange() setHelp( options ? getHelp(optionsList, val) @@ -267,18 +240,23 @@ const StepField = ({ subelement, current, generateValidation }) => { const StepTab = ({ previous, current, next }) => { const { configuration } = useDatasContext() - const generateValidation = (fieldData) => { - const validation = { - message: "", - valid: true, - modified: false, - } - return validation + const [reserveVersion, setReserveVersion] = useState(0) + const bumpFieldTree = () => { + setReserveVersion((version) => version + 1) } + const generateValidation = (fieldData) => + validateField( + fieldData, + useDatasContextFn.getValueId, + configuration.current + ) const [isLoading, setIsLoading] = useState(true) useEffect(() => { setIsLoading(false) }, []) + useEffect(() => { + bumpFieldTree() + }, [current]) return (
    {isLoading && } @@ -292,16 +270,18 @@ const StepTab = ({ previous, current, next }) => { return ( {element.value.map((subelement, subindex) => ( ))} diff --git a/src/translations/en.json b/src/translations/en.json index 3622a18..f7e1c74 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -10,5 +10,12 @@ "Others": "Others", "Download": "Download", "Previous": "Previous", - "Next": "Next" + "Next": "Next", + "Pin conflict warning": "Pin conflict warning", + "Some features share the same GPIO. This includes Default (-1) pins resolved for your MCU.": "Some features share the same GPIO. This includes Default (-1) pins resolved for your MCU.", + "Pin conflicts block download": "Pin conflicts — fix before download", + "Firmware downloads are disabled until each GPIO is used by only one visible feature. Default (-1) pins are resolved for your MCU.": "Firmware downloads are disabled until each GPIO is used by only one visible feature. Default (-1) pins are resolved for your MCU.", + "Download configuration.h": "Download configuration.h", + "Download platformio.ini": "Download platformio.ini", + "Download WebUI": "Download WebUI" } diff --git a/tools/validate_configuration.js b/tools/validate_configuration.js index 122ea76..7f9b200 100644 --- a/tools/validate_configuration.js +++ b/tools/validate_configuration.js @@ -96,18 +96,64 @@ for (const dep of depends) { } } -// Sections referenced by UI should have a matching JSON file +// Sections referenced by UI should have a matching JSON file and index export const stepsPath = path.join(__dirname, "..", "src", "pages", "config", "steps.js") +const indexPath = path.join(__dirname, "..", "src", "configuration", "index.js") const stepsSource = fs.readFileSync(stepsPath, "utf8") +const indexSource = fs.readFileSync(indexPath, "utf8") const sectionMatches = [...stepsSource.matchAll(/section:\s*"([^"]+)"/g)].map( (m) => m[1] ) +const indexSections = [ + ...indexSource.matchAll(/^\s+(\w+):\s+\w+Configuration/gm), +].map((m) => m[1]) + for (const section of sectionMatches) { if (section === "generate") continue - const expected = `${section}.json` - if (!jsonFiles.includes(expected) && section !== "default") { + const expected = section === "default" ? "defaults.json" : `${section}.json` + if (!jsonFiles.includes(expected)) { warnings.push(`configTabs section "${section}" has no ${expected}`) } + if (!indexSections.includes(section)) { + errors.push( + `configTabs section "${section}" is missing from configuration/index.js` + ) + } +} + +if (!indexSections.includes("default")) { + errors.push('configuration/index.js must export a "default" section') +} + +const mcuDefaultsPath = path.join( + __dirname, + "..", + "src", + "configuration", + "mcuPinDefaults.js" +) +const mcuSource = fs.readFileSync(mcuDefaultsPath, "utf8") +const mcuKeys = [ + ...mcuSource.matchAll(/^\s+(esp32\w*|esp8266|esp8285):\s*\{/gm), +].map((m) => m[1]) +const generatePath = path.join( + __dirname, + "..", + "src", + "tabs", + "generate", + "index.js" +) +const generateSource = fs.readFileSync(generatePath, "utf8") +for (const mcu of mcuKeys) { + if (!generateSource.includes(`${mcu}:`)) { + warnings.push(`mcuPinDefaults "${mcu}" has no matching section in generate/index.js`) + } + if (!generateSource.includes(`mcuPinDefaults.${mcu}`)) { + warnings.push( + `generate/index.js should spread mcuPinDefaults.${mcu} (single source for default GPIO)` + ) + } } if (warnings.length) { From ded3d024accfcf57ac3895bf4f42339037db19a8 Mon Sep 17 00:00:00 2001 From: Luc <8822552+luc-github@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:21:48 +0800 Subject: [PATCH 3/4] Better Pins management Allows export / import of the config Add Light/Auto/Dark mode Add preset feature to add easily new board on existing list --- .gitignore | 1 + API.md | 165 ++++++++ build/index.html | 2 +- docs/esp3d-camera-custom-pins.md | 14 + info.json | 2 +- package.json | 4 +- src/components/App/version.js | 2 +- src/components/BoardPresetPicker/index.js | 261 +++++++++++++ src/components/Helpers/components.js | 8 +- src/components/Modal/confirmModal.js | 8 +- src/components/Navbar/index.js | 4 + src/components/ThemeToggle/index.js | 51 +++ src/components/Toast/index.js | 43 ++- src/configuration/applyBoardPreset.js | 47 +++ src/configuration/boardPresets.js | 3 + src/configuration/boardPresets.json | 137 +++++++ src/configuration/cameraPinMaps.json | 65 ++++ src/configuration/cameraPins.js | 64 ++++ src/configuration/configurationSnapshot.js | 89 +++++ src/configuration/esp3dCameraModels.js | 21 ++ src/configuration/pinCapabilities.js | 215 +++++++++++ src/configuration/pinConflicts.js | 9 + src/configuration/pinUsageRoles.js | 19 + src/configuration/pinUsageRoles.json | 115 ++++++ src/configuration/psramReservedPins.js | 57 +++ src/configuration/reservedResources.js | 2 + src/configuration/serialPinDefaults.js | 163 ++++++++ src/configuration/serialPins.js | 69 ++++ src/configuration/tabs/devices.json | 416 ++++++++++++++++++++- src/configuration/tabs/features.json | 97 ++++- src/configuration/tabs/filesystems.json | 241 +++++++++--- src/configuration/tabs/network.json | 27 +- src/configuration/tabs/update.json | 25 +- src/configuration/validateField.js | 55 ++- src/contexts/DatasContext.js | 14 + src/contexts/UiContext.js | 11 +- src/index.html | 17 + src/index.js | 3 + src/style/_theme.scss | 157 ++++++++ src/style/components/_control.scss | 53 +++ src/style/components/_theme-toggle.scss | 46 +++ src/style/index.scss | 2 + src/tabs/generate/index.js | 73 ++-- src/tabs/step/index.js | 69 +++- src/theme/theme.js | 92 +++++ src/translations/en.json | 23 +- tools/check_esp3d_reference.js | 67 ++++ tools/migrate_camera_pins_to_select.js | 38 ++ tools/sync_pin_roles.js | 71 ++++ tools/validate_configuration.js | 101 ++++- 50 files changed, 3181 insertions(+), 157 deletions(-) create mode 100644 docs/esp3d-camera-custom-pins.md create mode 100644 src/components/BoardPresetPicker/index.js create mode 100644 src/components/ThemeToggle/index.js create mode 100644 src/configuration/applyBoardPreset.js create mode 100644 src/configuration/boardPresets.js create mode 100644 src/configuration/boardPresets.json create mode 100644 src/configuration/cameraPinMaps.json create mode 100644 src/configuration/cameraPins.js create mode 100644 src/configuration/configurationSnapshot.js create mode 100644 src/configuration/esp3dCameraModels.js create mode 100644 src/configuration/pinCapabilities.js create mode 100644 src/configuration/pinUsageRoles.js create mode 100644 src/configuration/pinUsageRoles.json create mode 100644 src/configuration/psramReservedPins.js create mode 100644 src/configuration/serialPinDefaults.js create mode 100644 src/configuration/serialPins.js create mode 100644 src/style/_theme.scss create mode 100644 src/style/components/_theme-toggle.scss create mode 100644 src/theme/theme.js create mode 100644 tools/check_esp3d_reference.js create mode 100644 tools/migrate_camera_pins_to_select.js create mode 100644 tools/sync_pin_roles.js diff --git a/.gitignore b/.gitignore index 9b47851..62ae93c 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ node_modules .vscode build/*.map +tmp/ \ No newline at end of file diff --git a/API.md b/API.md index 9e55385..7073e2a 100644 --- a/API.md +++ b/API.md @@ -91,6 +91,7 @@ Each item inside `group.value` is a control: | `type` | `text`, `number`, `select`, `boolean`, or `group` (nested) | | `value` | Default value (`boolean`, string, or `-1` for “none” on selects) | | `setting` | If `true`, emitted as `#define` in `configuration.h`; if `false`, comment only | +| `skipExport` | If `true` on a group, omitted from `configuration.h` (UI unchanged; values still in JSON snapshot) | | `define` | C macro name (e.g. `WIFI_FEATURE`) | | `depend` | Visibility rule (see below) | | `options` | For `select`: list of `{ label, value, help?, depend? }` | @@ -99,6 +100,10 @@ Each item inside `group.value` is a control: | `hide` | Skip in generated output | | `header` | Extra lines prepended before the define | | `ispin` | Use shared pin list (`src/tabs/step/pins.json`) | +| `pinRole` | MCU-independent usage: `output`, `input`, `bidirectional`, `analog_input`, `optional_gpio` (see `pinUsageRoles.json`; run `node tools/sync_pin_roles.js` to sync from registry) | +| `pinRoleWhen` | Optional override map, e.g. `{ "dependId": "sensortype", "map": { "ANALOG_DEVICE": "analog_input" } }` on `sensorpin` | +| `footnote` | Optional note below the control (e.g. **Memory type** boot warning on ESP32-S3) | +| `footnoteWhenActive` | If `true` on a boolean, `footnote` is shown only when the value is enabled | | `isport` | Use shared serial port list (`src/tabs/step/ports.json`) | | `usedefault` | For pin/port selects: show “Default” instead of “None” | | `usedescforoptions` | Repeat `description` as comment for select defines | @@ -156,11 +161,92 @@ On the **Download** tab: Settings in the `default` section are included in `configuration.h` even without a UI tab. +### Configuration snapshot (in `configuration.h` only) + +There is no separate settings file. **Download configuration.h** already embeds a JSON snapshot at the end of the file (block comment, markers `ESP3D_CONFIG_SNAPSHOT_BEGIN` … `ESP3D_CONFIG_SNAPSHOT_END`) with **all** wizard field ids and values (including `setting: false` fields such as serial/camera GPIO, `has_psram`, etc.). + +- **Save settings:** download `configuration.h` as today (`collectConfigurationSnapshot()` + `formatSnapshotComment()` in `src/configuration/configurationSnapshot.js`, appended in `configurationFile()`). +- **Restore:** **Features** tab → **Board preset** row → **Import configuration.h** (next to the preset list). The file name becomes a preset entry (selected by default after import). Re-selecting it uses the same confirmation flow as built-in presets. JSON is read from the comment block only (no `#define` parsing). UI refresh uses `configRevision` in `DatasContext`. + +Older `configuration.h` files without this block cannot be imported through this flow. + ## Maintenance notes - Avoid duplicate `id` values (e.g. multiple `ssdpmodelename` for different MCUs). Duplicates break `getValueId()` and can cause React key warnings; use distinct ids or a single control with `depend` on options. - After editing JSON, run `npm run validate:config` (duplicate ids, broken `depend`) then `npm run build` or `npm run dev`. +## Serial GPIO + +Two logical serial links (not three independent pin pairs): + +| Link | Port selector | GPIO overrides | +|------|---------------|----------------| +| **Main** (printer/TFT) | **Main serial port** → `ESP_SERIAL_OUTPUT`, then **RX** / **TX** | `ESP_RX_PIN`, `ESP_TX_PIN` | +| **Serial bridge** (optional, ESP32 family) | **Use serial bridge** + **Serial bridge port** → `ESP_SERIAL_BRIDGE_OUTPUT`, then **RX** / **TX** | `ESP_BRIDGE_RX_PIN`, `ESP_BRIDGE_TX_PIN` | + +All of these live in the **Serial Communications** group (pins directly under each port selector). + +Use **Default** (`-1`) on RX/TX selects to keep the board default for that UART (define not emitted; `esp3d_pins.h` supplies `-1` defaults). In the UI, Default is shown as **-1 (GPIO n)** using typical UART pins for the selected **Main serial port** or **Serial bridge port** (`serialPinDefaults.js`) so pin conflict tracking matches other tabs. + +Non-default GPIO values are appended to `configuration.h` with the same guards as ESP3D: + +- Main: `#ifndef ESP_RX_PIN` / `ESP_TX_PIN` then `#define` (always valid once `ESP_SERIAL_OUTPUT` is set). +- Bridge: wrapped in `#if defined(ESP_SERIAL_BRIDGE_OUTPUT)` … `#endif`, each pin under `#ifndef ESP_BRIDGE_RX_PIN` / `ESP_BRIDGE_TX_PIN` (only when **Use serial bridge** is enabled). `ESP_SERIAL_BRIDGE_OUTPUT` must appear earlier in the file (from **Serial bridge port**). + +**UART choices** depend on MCU (`src/tabs/step/ports.json`): ESP32/S3 may use UART 0/1/2 on the main port; bridge uses another UART from the same list when enabled. On **ESP32 with PSRAM**, UART 2 must not use GPIO 16/17 (module PSRAM lines); the configurator no longer suggests those as serial defaults when PSRAM (or a camera) is enabled. + +## PSRAM reserved GPIO + +When **Has PSRAM** is enabled, or a **camera** is selected (camera requires PSRAM), `psramReservedPins.js` adds module-reserved GPIO to pin tracking and conflict checks: + +| MCU | Reserved GPIO (typical module wiring) | +|-----|--------------------------------------| +| ESP32 | 16, 17 (PSRAM CS / clock on WROVER-class modules) | +| ESP32-S2 | 26–31 (SPI flash + PSRAM bus) | +| ESP32-S3 | 26–32; + 33–37 if **Memory type** is octal PSRAM (`dio_opi`, `qio_opi`, `opi_opi`) | + +ESP8266 has no PSRAM option. Exact pins depend on the physical module; do not assign these GPIO to SD, TFT, serial overrides, etc. + +## Camera + +On the **Devices** tab, the **Camera** group is visible only when **Has PSRAM** is enabled on Features (ESP32 / ESP32-S2 / ESP32-S3, not ESP-01). ESP3D only supports cameras on boards with PSRAM. + +- **Camera type** → `#define CAMERA_DEVICE` in `configuration.h`. +- **Camera GPIO map** (Devices): read-only for predefined models (`cameraPinMaps.json`). **Custom** enables editing; GPIO values are written to `configuration.h`. +- **Flip** options → `CAMERA_DEVICE_FLIP_*` defines. +- Choosing a camera model still turns **Has PSRAM** on automatically; `platformio.ini` adds `-DBOARD_HAS_PSRAM` and related flags when a model is selected. + +`platformio.ini` adds a `_cam` suffix to the environment name and uses `min_spiffs.csv` on 4 MB flash when a camera is enabled (aligned with `[env:esp32cam]` in ESP3D’s `platformio.ini`). + +### ESP3D reference sources (`tmp/`) + +Drop a copy of [ESP3D](https://github.com/luc-github/ESP3D) under `tmp/esp3d` (with `tmp/platformio.ini`) to compare against the upstream firmware. Then run: + +```bash +npm run check:esp3d +``` + +This checks that every `CAMERA_MODEL_*` in `tmp/esp3d/src/include/esp3d_defines.h` appears in the configurator UI. + +## Board presets + +Starting profiles for common boards live in `src/configuration/boardPresets.json`. Each preset has: + +| Field | Description | +|-------|-------------| +| `id` | Unique preset id | +| `label` | Name shown in the UI | +| `description` | Short help text | +| `values` | Map of **field id** → value (same ids as in tab JSON, across all sections) | + +The picker on the **Features** tab applies a preset when you change the dropdown (confirmation dialog). **Cancel** restores the previous selection without applying. It overwrites only the fields listed in `values`; other settings are left unchanged. + +To add a preset: + +1. Add an entry to `boardPresets.json` (use only existing field ids). +2. Run `npm run validate:config`. +3. Test in the UI on Features → **Apply preset**. + ## Quick checklist: new feature flag 1. Add the control under the right `tabs/
    .json` group (or new group). @@ -171,3 +257,82 @@ Settings in the `default` section are included in `configuration.h` even without ## Planned (not implemented) - **Immutable configuration state** — field values are still updated in place on the loaded JSON (`subelement.value = …`). A future refactor would use immutable updates for clearer React updates, undo, and tests. + +### MCU profiles (trigger when adding a new MCU) + +**Not urgent today.** Recommended when the next chip is added (e.g. a future **ESP32-S31** or any MCU not covered by existing `depend` lists), instead of extending `pins.json`, `ports.json`, and tab `depend` arrays across many files. + +**Goal:** one central profile per MCU; shared wizard tabs stay common; fewer `depend: { id: "targetmcu", … }` blocks. + +**Intended layout (sketch):** + +```text +src/configuration/mcu/ + _registry.json # mcu id, label, supported in UI + _common/ # network, security, others, … (unchanged) + esp32/profile.json # pins[], serial ports[], flash options, SPI defaults, reserved GPIO rules + esp32s3/profile.json + … +``` + +**Per-MCU profile would own:** GPIO picker list, UART choices, flash size / `flash_mode` / `memory_type` options, `mcuPinDefaults`-style values, PSRAM/flash bus reservations (today split across `pins.json`, `ports.json`, `mcuPinDefaults.js`, `pinCapabilities.js`, `psramReservedPins.js`). + +**Must stay global:** field **ids** (`wifi`, `sdmosipin`, …), snapshot import/export, `boardPresets.json` keys, and `#define` names — only options and visibility merge from the active profile. + +**Runtime:** `buildConfiguration(targetmcu)` = `_common` + active `mcu//profile` (regenerate or filter UI when MCU changes; validate incompatible values after switch). + +**Phased rollout:** (1) profiles for pins + defaults + rules, (2) feature tab overlays, (3) `validate:config` per profile. Until then, new MCUs follow the current pattern (`depend` + updates to the JS modules above). + +### GPIO capabilities per field (input / output / ADC) + +Reference list (MCU-independent): **`src/configuration/pinUsageRoles.json`** — every `ispin` / camera GPIO field mapped to a **field role**. + +**Two layers (do not mix):** + +| Layer | Purpose | Values | +|-------|---------|--------| +| **Field role** | What the feature needs | `output`, `input`, `bidirectional`, `analog_input`, `optional_gpio` | +| **Hardware tag** | What the chip/module allows (per MCU, later) | `reserved`, `input_only`, `output_only`, `adc`, `bidirectional` | + +**Your proposal vs recommended minimum:** + +| Your label | Verdict | +|------------|---------| +| Any (Input/Output/ADC) | Too broad — does not block buzzer on GPIO 34. | +| Input/Output (Not ADC) | ≈ **`bidirectional`** for SPI/SDIO/MDIO/I2C. | +| Input only / Output only | **Hardware tags**, not field roles — filter pickers (RX → not `output_only`; buzzer → not `input_only`). | +| Input only ADC | **Hardware tag** — only for `analog_input` fields (`sensorpin` when `ANALOG_DEVICE`). | +| Output only ADC | **Unused** on ESP32 (no ADC output); DAC could be a future `dac_output` role. | +| Reserved | **Hardware tag** — PSRAM/flash/strapping (`psramReservedPins.js`), not assigned on fields. | + +**Counts in `pinUsageRoles.json` (54 GPIO fields):** 22× `output`, 17× `input`, 13× `bidirectional`, 2× `optional_gpio`; **`analog_input`** only via **`sensorpin`** + `roleWhen` when `sensortype=ANALOG_DEVICE` (DHT → `bidirectional`). + +**Implemented:** `src/configuration/pinCapabilities.js` filters `pins.json` options in `StepField` and validates in `validateField` from `pinRole` + MCU rules (flash bus, input-only, ADC, PSRAM/octal reserved). Registry: `pinUsageRoles.json`; sync with `npm run sync:pin-roles`. + +**Camera GPIO** (`camerapins` group): all pins are `select` + `ispin` + `pinRole`; lists filtered like other features. **Custom** model is editable; predefined maps stay read-only but still participate in conflict audit. + +**Still TODO:** strapping warnings; finer C3/C6 module profiles. + +### Flash mode & PSRAM memory type vs reserved GPIO + +**Current behaviour:** + +| Setting | UI | `platformio.ini` | Reserved GPIO logic | +|---------|----|------------------|---------------------| +| `flash_mode` | Features (all ESP32 targets) | All ESP32 envs: `board_build.flash_mode = $flash_mode` | **Not** tied to pin reservation yet | +| `memory_type` | Features, **ESP32-S3 only**, if `has_psram` | **ESP32-S3 only**: `board_build.arduino.memory_type = $memory_type` | **S3 only**: extra GPIO 33–37 when value is `dio_opi` / `qio_opi` / `opi_opi` (`psramReservedPins.js`) | + +**MCU notes (to validate against module/schematic):** + +- **ESP32 (classic):** PSRAM (WROVER) → GPIO **16–17**; flash/PSRAM SPI often **6–11** on module. `flash_mode` (dio/qio/out) changes bus width, not a different GPIO list in the configurator today. +- **ESP32-S3:** **Most sensitive** — octal flash + octal PSRAM share **26–32** and often **33–37**; `memory_type` must stay consistent with the physical module (N8R2 vs N16R8, etc.). Mismatch → boot failure, not just a pin conflict in UI. +- **ESP32-S2:** `memory_type` **not** in UI; PSRAM reservation **26–31** when `has_psram` (same SPI bus idea as S3, no octal extra pins in tool yet). `flash_mode` still emitted. +- **ESP32-C3 / C6:** `memory_type` **not** in UI; `has_psram` exists but **no** extra reserved GPIO in `psramReservedPins.js` yet — needs IDF/module matrix (some C3 modules use different lines than S3). `flash_mode` still emitted. + +**Follow-up work:** + +1. Document allowed `(targetmcu, flash_mode, memory_type)` combinations (presets + warnings). +2. Link **octal** `memory_type` on S3 to reserved GPIO 33–37 (done) and optionally hide those pins from pickers. +3. Decide whether S2/C3/C6 need a `memory_type`-like control or only preset-driven `platformio` strings. +4. Optionally reserve **GPIO 6–11** on ESP32 whenever flash is internal (independent of `has_psram`). +5. Cross-check `board_presets.json` (e.g. S3 `qio` + `qio_opi`) against Espressif board definitions in `tmp/platformio.ini` / ESP3D upstream. diff --git a/build/index.html b/build/index.html index 75d1fd8..5bf3cb5 100644 --- a/build/index.html +++ b/build/index.html @@ -1 +1 @@ -ESP3D-Configurator \ No newline at end of file +ESP3D-Configurator \ No newline at end of file diff --git a/docs/esp3d-camera-custom-pins.md b/docs/esp3d-camera-custom-pins.md new file mode 100644 index 0000000..94640ef --- /dev/null +++ b/docs/esp3d-camera-custom-pins.md @@ -0,0 +1,14 @@ +# ESP3D — Camera CUSTOM pins and configurator + +When **Camera type = Custom**, the configurator emits camera GPIO overrides in `configuration.h`: + +```c +#define CAMERA_DEVICE CAMERA_MODEL_CUSTOM +#define PWDN_GPIO_NUM 32 +#define XCLK_GPIO_NUM 0 +// ... +``` + +ESP3D loads `configuration.h` before `esp3d_pins.h`. The firmware **CUSTOM** pin block must use `#ifndef` / `#define` / `#endif` so these values take effect (see ESP3D `esp3d/src/include/esp3d_pins.h`). + +Predefined models use fixed maps in firmware; the configurator shows those pins read-only from `src/configuration/cameraPinMaps.json`. diff --git a/info.json b/info.json index b5ec617..78dc091 100644 --- a/info.json +++ b/info.json @@ -1,3 +1,3 @@ { - "version": "1.0.34" + "version": "1.1.0" } diff --git a/package.json b/package.json index 1ca0d17..97f2b8f 100644 --- a/package.json +++ b/package.json @@ -9,7 +9,9 @@ "server": "nodemon config/server.js", "dev": "concurrently \"npm run server\" \"npm run front\"", "build": "webpack --config config/webpack.prod.js", - "validate:config": "node tools/validate_configuration.js" + "validate:config": "node tools/validate_configuration.js", + "sync:pin-roles": "node tools/sync_pin_roles.js", + "check:esp3d": "node tools/check_esp3d_reference.js" }, "author": "", "license": "ISC", diff --git a/src/components/App/version.js b/src/components/App/version.js index 2912d7c..eef0ffe 100644 --- a/src/components/App/version.js +++ b/src/components/App/version.js @@ -16,4 +16,4 @@ Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA */ import { h } from "preact" -export const Version = "1.0.34" +export const Version = "1.1.0" diff --git a/src/components/BoardPresetPicker/index.js b/src/components/BoardPresetPicker/index.js new file mode 100644 index 0000000..25114ee --- /dev/null +++ b/src/components/BoardPresetPicker/index.js @@ -0,0 +1,261 @@ +import { h } from "preact" +import { useState } from "preact/hooks" +import { Upload } from "preact-feather" +import { Field, FieldGroup, ButtonImg } from "../Controls" +import { T } from "../Translations" +import { useDatasContext, useUiContext } from "../../contexts" +import { showConfirmationModal } from "../Modal" +import boardPresets from "../../configuration/boardPresets" +import { applyBoardPreset, getBoardPresetById } from "../../configuration/applyBoardPreset" +import { + parseSnapshotFromText, + snapshotImportErrorMessage, +} from "../../configuration/configurationSnapshot" + +const IMPORTED_PRESET_ID = "imported-configuration-h" +const IMPORT_FILE_INPUT_ID = "board-preset-import-h" + +const presetListWithImported = (importedPreset) => + importedPreset ? [importedPreset, ...boardPresets] : boardPresets + +const labelFromImportFile = (filename) => { + const base = filename.replace(/^.*[/\\]/, "").trim() + return base || T("Imported configuration") +} + +const buildImportSuccessMessage = (applied, label, unknownCount = 0) => { + const detail = + unknownCount > 0 + ? ` (${unknownCount} ${T("unknown ids ignored")})` + : "" + return `${T("Imported")} ${applied} ${T("settings from")} ${label}${detail}.` +} + +const showImportToast = (toasts, variant, content, literal = false) => { + if (!content) return + toasts.addToast({ + type: variant === "success" ? "success" : "error", + content, + literal, + }) +} + +const presetConfirmContent = (preset) => ( +
    +

    + {T( + "This will overwrite settings that are defined in the preset. Other tabs are included." + )} +

    +

    + {preset.label} +

    + {preset.description &&

    {preset.description}

    } +
    +) + +const BoardPresetPicker = ({ onApplied }) => { + const { configuration, importConfigurationSnapshot } = useDatasContext() + const { modals, toasts } = useUiContext() + const [activeId, setActiveId] = useState("") + const [importedPreset, setImportedPreset] = useState(null) + const [importFeedback, setImportFeedback] = useState(null) + + const allPresets = presetListWithImported(importedPreset) + const activePreset = getBoardPresetById(allPresets, activeId) + + const applyPreset = (preset, newActiveId) => { + const result = + preset.id === IMPORTED_PRESET_ID + ? importConfigurationSnapshot({ values: preset.values }) + : applyBoardPreset(configuration.current, preset) + setActiveId(newActiveId) + onApplied?.({ presetId: preset.id }) + return result + } + + const finishImportedPresetApply = (preset) => { + setImportedPreset(preset) + const { applied, unknown } = applyPreset(preset, IMPORTED_PRESET_ID) + const successMsg = buildImportSuccessMessage( + applied, + preset.label, + unknown.length + ) + setImportFeedback({ + variant: "success", + message: successMsg, + }) + showImportToast(toasts, "success", successMsg, true) + } + + const confirmApplyPreset = (preset, newActiveId) => { + showConfirmationModal({ + modals, + title: T("Apply board preset"), + content: presetConfirmContent(preset), + button1: { text: T("Cancel") }, + button2: { + text: T("Apply"), + cb: () => { + if (preset.id === IMPORTED_PRESET_ID) { + finishImportedPresetApply(preset) + } else { + applyPreset(preset, newActiveId) + } + }, + }, + }) + } + + const confirmBeforeImport = () => { + const importPreset = { + id: IMPORTED_PRESET_ID, + label: T("Import configuration.h"), + description: T( + "You will choose a configuration.h file generated by this tool (snapshot at end of file)." + ), + } + showConfirmationModal({ + modals, + title: T("Apply board preset"), + content: presetConfirmContent(importPreset), + button1: { text: T("Cancel") }, + button2: { + text: T("Apply"), + cb: () => { + document.getElementById(IMPORT_FILE_INPUT_ID)?.click() + }, + }, + }) + } + + const reportImportError = (message) => { + const text = T(message) + setImportFeedback({ variant: "error", message: text }) + showImportToast(toasts, "error", text, true) + } + + const handleSelectChange = (newId) => { + if (newId === activeId) return + + if (!newId) { + setActiveId("") + return + } + + const preset = getBoardPresetById(allPresets, newId) + if (!preset) return + + confirmApplyPreset(preset, newId) + } + + const handleImportFile = (file) => { + if (!file) return + setImportFeedback(null) + + const reader = new FileReader() + reader.onerror = () => { + reportImportError("Could not read the selected file.") + } + reader.onload = () => { + const parsed = parseSnapshotFromText(String(reader.result || "")) + if (!parsed.ok) { + reportImportError(snapshotImportErrorMessage(parsed)) + return + } + + const preset = { + id: IMPORTED_PRESET_ID, + label: labelFromImportFile(file.name), + description: T( + "Imported from configuration.h (embedded snapshot)" + ), + values: parsed.snapshot.values, + } + + finishImportedPresetApply(preset) + } + reader.readAsText(file) + } + + if (!boardPresets.length) return null + + const presetOptions = [ + { label: T("None (current settings)"), value: "" }, + ...(importedPreset + ? [{ label: importedPreset.label, value: importedPreset.id }] + : []), + ...boardPresets.map((preset) => ({ + label: preset.label, + value: preset.id, + })), + ] + + const feedbackColor = + importFeedback?.variant === "success" + ? "#32b643" + : importFeedback?.variant === "error" + ? "#ff0000" + : undefined + + return ( + +

    + {T( + "Choose a board profile or import configuration.h (confirmation required). Fine-tune in each tab afterward." + )} +

    +
    + { + if (update) return + handleSelectChange(val) + }} + /> + { + const file = e.target.files && e.target.files[0] + handleImportFile(file) + e.target.value = "" + }} + /> + } + label={T("Import configuration.h")} + onclick={confirmBeforeImport} + /> +
    + {importFeedback && ( +

    + {importFeedback.message} +

    + )} + {activePreset?.description && ( +

    {activePreset.description}

    + )} +
    + ) +} + +export { BoardPresetPicker, IMPORTED_PRESET_ID } diff --git a/src/components/Helpers/components.js b/src/components/Helpers/components.js index 533334f..b83ee68 100644 --- a/src/components/Helpers/components.js +++ b/src/components/Helpers/components.js @@ -31,7 +31,7 @@ const generateUID = () => Math.random().toString(36).substr(2, 9) const createComponent = (is, className, classModifier = {}) => - ({ is: Tag = is, class: c = "", id = "", ...props }) => { + ({ is: Tag = is, class: c = "", id = "", children, ...props }) => { const splittedArgs = Object.keys(props).reduce( (acc, curr) => { if (Object.keys(classModifier).includes(curr)) @@ -49,7 +49,11 @@ const createComponent = const classNames = `${className} ${splittedArgs.classes.join( " " )} ${c}`.trim() - return + return ( + + {children} + + ) } /* diff --git a/src/components/Modal/confirmModal.js b/src/components/Modal/confirmModal.js index e57e3ea..c4613bf 100644 --- a/src/components/Modal/confirmModal.js +++ b/src/components/Modal/confirmModal.js @@ -19,6 +19,8 @@ import { h } from "preact" import { HelpCircle } from "preact-feather" import { showModal } from "./genericModal" +const CONFIRMATION_MODAL_ID = "confirmation" + const showConfirmationModal = ({ modals, title, @@ -26,13 +28,17 @@ const showConfirmationModal = ({ button1, button2, }) => { + const existing = modals.getModalIndex(CONFIRMATION_MODAL_ID) + if (existing !== -1) { + modals.removeModal(existing) + } showModal({ modals, title, content, button1, button2, - id: "confirmation", + id: CONFIRMATION_MODAL_ID, icon: , hideclose: true, }) diff --git a/src/components/Navbar/index.js b/src/components/Navbar/index.js index f215207..8e898f1 100644 --- a/src/components/Navbar/index.js +++ b/src/components/Navbar/index.js @@ -24,6 +24,7 @@ import { Settings } from "preact-feather" import { AppLogo } from "../Images/logo" import { Link } from "../Router" import { T } from "../Translations" +import { ThemeToggle } from "../ThemeToggle" /* * Local const @@ -73,6 +74,9 @@ const Navbar = () => { ) })}
    + ) } diff --git a/src/components/ThemeToggle/index.js b/src/components/ThemeToggle/index.js new file mode 100644 index 0000000..844216e --- /dev/null +++ b/src/components/ThemeToggle/index.js @@ -0,0 +1,51 @@ +import { h } from "preact" +import { useState, useEffect } from "preact/hooks" +import { Sun, Moon, Monitor } from "preact-feather" +import { getStoredThemeMode, setThemeMode } from "../../theme/theme" + +const MODES = [ + { id: "light", Icon: Sun, label: "Light theme" }, + { id: "auto", Icon: Monitor, label: "System theme" }, + { id: "dark", Icon: Moon, label: "Dark theme" }, +] + +const ThemeToggle = () => { + const [mode, setMode] = useState(getStoredThemeMode) + + useEffect(() => { + const onStorage = (e) => { + if (e.key === "esp3d-configurator-ui-theme") { + setMode(getStoredThemeMode()) + } + } + window.addEventListener("storage", onStorage) + return () => window.removeEventListener("storage", onStorage) + }, []) + + const select = (next) => { + if (next === mode) return + setThemeMode(next) + setMode(next) + } + + return ( +
    + {MODES.map(({ id, Icon, label }) => ( + + ))} +
    + ) +} + +export { ThemeToggle } diff --git a/src/components/Toast/index.js b/src/components/Toast/index.js index bd4ccd2..2a80576 100644 --- a/src/components/Toast/index.js +++ b/src/components/Toast/index.js @@ -17,15 +17,18 @@ */ import { h } from "preact" import { useEffect } from "preact/hooks" -import { useUiContext } from "../../contexts" -import { Toast as SpectreToast } from "../Controls" +import { useUiContext, useUiContextFn } from "../../contexts" import { T } from "../Translations" -/* - * Local const - * - */ -const Toast = ({ index, type = "", children, timeout = 2000, remove }) => { +const modifierClass = (type) => { + if (type === "success") return "toast-success" + if (type === "error") return "toast-error" + if (type === "warning") return "toast-warning" + if (type === "primary") return "toast-primary" + return "" +} + +const Toast = ({ index, type = "", message = "", timeout = 2000, remove }) => { useEffect(() => { let timer if (timeout) { @@ -36,16 +39,21 @@ const Toast = ({ index, type = "", children, timeout = 2000, remove }) => { } }, []) + const variantClass = modifierClass(type) + return ( - - + {message} +
    ) } @@ -55,16 +63,21 @@ const ToastsContainer = () => { toasts.toastList && (
    {toasts.toastList.map((toast) => { - const { id, type, content } = toast + const { id, type, content, message, literal } = toast + const text = content ?? message + const label = text + ? literal + ? String(text) + : T(text) + : "" return ( - {T(content)} - + /> ) })}
    diff --git a/src/configuration/applyBoardPreset.js b/src/configuration/applyBoardPreset.js new file mode 100644 index 0000000..3618426 --- /dev/null +++ b/src/configuration/applyBoardPreset.js @@ -0,0 +1,47 @@ +import { syncCameraPinsForModel } from "./cameraPins" + +const forEachField = (configuration, visitor) => { + if (!configuration) return + Object.keys(configuration).forEach((section) => { + const groups = configuration[section] + if (!Array.isArray(groups)) return + groups.forEach((group) => { + if (group.type === "group" && Array.isArray(group.value)) { + group.value.forEach((field) => visitor(field, section, group)) + } + }) + }) +} + +const setFieldValueById = (configuration, fieldId, value) => { + let found = false + forEachField(configuration, (field) => { + if (field.id === fieldId) { + field.value = value + found = true + } + }) + return found +} + +const applyBoardPreset = (configuration, preset) => { + const unknown = [] + const values = preset.values || {} + let applied = 0 + Object.entries(values).forEach(([fieldId, value]) => { + if (setFieldValueById(configuration, fieldId, value)) { + applied += 1 + } else { + unknown.push(fieldId) + } + }) + if (values.cameratype && values.cameratype !== "-1") { + syncCameraPinsForModel(configuration, values.cameratype) + } + return { applied, unknown, presetId: preset.id } +} + +const getBoardPresetById = (presets, id) => + presets.find((preset) => preset.id === id) + +export { applyBoardPreset, setFieldValueById, getBoardPresetById, forEachField } diff --git a/src/configuration/boardPresets.js b/src/configuration/boardPresets.js new file mode 100644 index 0000000..1203140 --- /dev/null +++ b/src/configuration/boardPresets.js @@ -0,0 +1,3 @@ +import boardPresets from "./boardPresets.json" + +export default boardPresets diff --git a/src/configuration/boardPresets.json b/src/configuration/boardPresets.json new file mode 100644 index 0000000..f04140a --- /dev/null +++ b/src/configuration/boardPresets.json @@ -0,0 +1,137 @@ +[ + { + "id": "esp32-devkit-4mb", + "label": "ESP32 devkit (4 MB)", + "description": "Generic ESP32 module, 4 MB flash, full pinout, WiFi, no SD or camera.", + "values": { + "targetmcu": "esp32", + "targetflashsize": "4", + "formfactor": "original", + "flash_mode": "dio", + "has_psram": false, + "wifi": true, + "ethernet": false, + "sdconnection": "-1", + "cameratype": "-1", + "displaytype": "-1", + "systemtype": "Printer3D", + "defaultfw": "MARLIN", + "serialprotocol": "RAW_SERIAL", + "serialport": "USE_SERIAL_0" + } + }, + { + "id": "esp32-devkit-sd", + "label": "ESP32 devkit + SD (SPI)", + "description": "ESP32 with dedicated SD card on SPI (MCU default pins).", + "values": { + "targetmcu": "esp32", + "targetflashsize": "4", + "formfactor": "original", + "flash_mode": "dio", + "has_psram": false, + "wifi": true, + "ethernet": false, + "sdconnection": "ESP_NOT_SHARED_SD", + "sdfs": "ESP_SDFAT2", + "sdcspin": "-1", + "sdmosipin": "-1", + "sdmisopin": "-1", + "sdsckpin": "-1", + "cameratype": "-1", + "displaytype": "-1", + "systemtype": "Printer3D", + "defaultfw": "MARLIN", + "serialprotocol": "RAW_SERIAL", + "serialport": "USE_SERIAL_0" + } + }, + { + "id": "esp32-cam-ai-thinker", + "label": "ESP32-CAM (AI-Thinker)", + "description": "ESP32 with PSRAM and AI-Thinker camera module.", + "values": { + "targetmcu": "esp32", + "targetflashsize": "4", + "formfactor": "original", + "flash_mode": "dio", + "has_psram": true, + "wifi": true, + "ethernet": false, + "cameratype": "CAMERA_MODEL_AI_THINKER", + "sdconnection": "ESP_NOT_SHARED_SD", + "sdfs": "ESP_SD_NATIVE", + "displaytype": "-1", + "systemtype": "Printer3D", + "defaultfw": "MARLIN", + "serialprotocol": "RAW_SERIAL", + "serialport": "USE_SERIAL_0" + } + }, + { + "id": "esp8266-esp01-1mb", + "label": "ESP8266 ESP-01 (1 MB)", + "description": "ESP-01 form factor, 1 MB flash, WiFi only.", + "values": { + "targetmcu": "esp8266", + "targetflashsize": "1", + "formfactor": "esp01", + "flash_mod1": "dio", + "resetmethod": "ck", + "wifi": true, + "sdconnection": "-1", + "cameratype": "-1", + "displaytype": "-1", + "systemtype": "Printer3D", + "defaultfw": "MARLIN", + "serialprotocol": "RAW_SERIAL", + "serialport": "USE_SERIAL_0" + } + }, + { + "id": "esp32s3-devkit-8mb", + "label": "ESP32-S3 devkit (8 MB)", + "description": "ESP32-S3 with 8 MB flash and octal PSRAM profile, no camera.", + "values": { + "targetmcu": "esp32s3", + "targetflashsize": "8", + "formfactor": "original", + "flash_mode": "qio", + "has_psram": true, + "memory_type": "qio_opi", + "wifi": true, + "ethernet": false, + "usbserial": false, + "sdconnection": "-1", + "cameratype": "-1", + "displaytype": "-1", + "systemtype": "Printer3D", + "defaultfw": "MARLIN", + "serialprotocol": "RAW_SERIAL", + "serialport": "USE_SERIAL_0" + } + }, + { + "id": "esp32s3-xiao-sense", + "label": "ESP32-S3 (Seeed Xiao Sense + camera)", + "description": "ESP32-S3 devkit profile with PSRAM and Xiao ESP32-S3 Sense camera pin map.", + "values": { + "targetmcu": "esp32s3", + "targetflashsize": "8", + "formfactor": "original", + "flash_mode": "qio", + "has_psram": true, + "memory_type": "qio_opi", + "wifi": true, + "ethernet": false, + "usbserial": false, + "sdconnection": "-1", + "cameratype": "CAMERA_MODEL_XIAO_ESP32S3", + "displaytype": "-1", + "systemtype": "Printer3D", + "defaultfw": "MARLIN", + "serialprotocol": "RAW_SERIAL", + "serialport": "USE_SERIAL_0" + } + } +] diff --git a/src/configuration/cameraPinMaps.json b/src/configuration/cameraPinMaps.json new file mode 100644 index 0000000..1495c88 --- /dev/null +++ b/src/configuration/cameraPinMaps.json @@ -0,0 +1,65 @@ +{ + "CAMERA_MODEL_AI_THINKER": { + "CAM_LED_PIN": -1, + "CAM_PULLUP1": -1, + "CAM_PULLUP2": -1, + "PWDN_GPIO_NUM": 32, + "RESET_GPIO_NUM": -1, + "XCLK_GPIO_NUM": 0, + "SIOD_GPIO_NUM": 26, + "SIOC_GPIO_NUM": 27, + "Y9_GPIO_NUM": 35, + "Y8_GPIO_NUM": 34, + "Y7_GPIO_NUM": 39, + "Y6_GPIO_NUM": 36, + "Y5_GPIO_NUM": 21, + "Y4_GPIO_NUM": 19, + "Y3_GPIO_NUM": 18, + "Y2_GPIO_NUM": 5, + "VSYNC_GPIO_NUM": 25, + "HREF_GPIO_NUM": 23, + "PCLK_GPIO_NUM": 22 + }, + "CAMERA_MODEL_XIAO_ESP32S3": { + "CAM_LED_PIN": -1, + "CAM_PULLUP1": -1, + "CAM_PULLUP2": -1, + "PWDN_GPIO_NUM": -1, + "RESET_GPIO_NUM": -1, + "XCLK_GPIO_NUM": 10, + "SIOD_GPIO_NUM": 40, + "SIOC_GPIO_NUM": 39, + "Y9_GPIO_NUM": 48, + "Y8_GPIO_NUM": 11, + "Y7_GPIO_NUM": 12, + "Y6_GPIO_NUM": 14, + "Y5_GPIO_NUM": 16, + "Y4_GPIO_NUM": 18, + "Y3_GPIO_NUM": 17, + "Y2_GPIO_NUM": 15, + "VSYNC_GPIO_NUM": 38, + "HREF_GPIO_NUM": 47, + "PCLK_GPIO_NUM": 13 + }, + "CAMERA_MODEL_CUSTOM": { + "CAM_LED_PIN": 4, + "CAM_PULLUP1": -1, + "CAM_PULLUP2": -1, + "PWDN_GPIO_NUM": 32, + "RESET_GPIO_NUM": -1, + "XCLK_GPIO_NUM": 0, + "SIOD_GPIO_NUM": 26, + "SIOC_GPIO_NUM": 27, + "Y9_GPIO_NUM": 35, + "Y8_GPIO_NUM": 34, + "Y7_GPIO_NUM": 39, + "Y6_GPIO_NUM": 36, + "Y5_GPIO_NUM": 21, + "Y4_GPIO_NUM": 19, + "Y3_GPIO_NUM": 18, + "Y2_GPIO_NUM": 5, + "VSYNC_GPIO_NUM": 25, + "HREF_GPIO_NUM": 23, + "PCLK_GPIO_NUM": 22 + } +} diff --git a/src/configuration/cameraPins.js b/src/configuration/cameraPins.js new file mode 100644 index 0000000..cc40174 --- /dev/null +++ b/src/configuration/cameraPins.js @@ -0,0 +1,64 @@ +import cameraPinMaps from "./cameraPinMaps.json" + +const CAMERA_PIN_KEYS = Object.keys(cameraPinMaps.CAMERA_MODEL_CUSTOM) + +const isCustomCameraModel = (model) => model === "CAMERA_MODEL_CUSTOM" + +const getCameraPinMap = (model) => { + if (!model || model === "-1") return null + return cameraPinMaps[model] || null +} + +const syncCameraPinsForModel = (configuration, model) => { + const pins = getCameraPinMap(model) + if (!pins || !configuration) return + CAMERA_PIN_KEYS.forEach((key) => { + if (pins[key] === undefined) return + for (const section of Object.keys(configuration)) { + const groups = configuration[section] + if (!Array.isArray(groups)) continue + for (const group of groups) { + if (!Array.isArray(group.value)) continue + for (const field of group.value) { + if (field.id === key) { + const v = pins[key] + field.value = + v === -1 || v === "-1" ? "-1" : String(v) + } + } + } + } + }) +} + +const formatCameraPinDefines = (configuration, getValueId) => { + const model = getValueId("cameratype") + if (!isCustomCameraModel(model)) return "" + let block = + "\n/************************************\n" + + "*\n* Camera GPIO (CAMERA_MODEL_CUSTOM)\n" + + "*\n* Custom camera GPIO overrides (configuration.h)\n" + + "*\n************************************/\n" + CAMERA_PIN_KEYS.forEach((key) => { + const val = getValueId(key) + if ( + val === undefined || + val === "" || + val == -1 || + val == "-1" + ) { + return + } + block += `#define ${key} ${val}\n` + }) + return block +} + +export { + CAMERA_PIN_KEYS, + cameraPinMaps, + isCustomCameraModel, + getCameraPinMap, + syncCameraPinsForModel, + formatCameraPinDefines, +} diff --git a/src/configuration/configurationSnapshot.js b/src/configuration/configurationSnapshot.js new file mode 100644 index 0000000..2c6402e --- /dev/null +++ b/src/configuration/configurationSnapshot.js @@ -0,0 +1,89 @@ +import { Version } from "../components/App/version" +import { applyBoardPreset, forEachField } from "./applyBoardPreset" + +const SNAPSHOT_BEGIN = "ESP3D_CONFIG_SNAPSHOT_BEGIN" +const SNAPSHOT_END = "ESP3D_CONFIG_SNAPSHOT_END" +const SNAPSHOT_VERSION = 1 + +const collectConfigurationSnapshot = (configuration) => { + const values = {} + forEachField(configuration, (field) => { + if (!field.id) return + values[field.id] = field.value + }) + return { + version: SNAPSHOT_VERSION, + generator: "ESP3D-Configurator", + configuratorVersion: Version, + values, + } +} + +const formatSnapshotComment = (snapshot) => + "\n\n/************************************\n" + + " * ESP3D-Configurator settings snapshot\n" + + " * Re-import: Features tab → Board preset → Import configuration.h\n" + + " ************************************/\n" + + `/* ${SNAPSHOT_BEGIN}\n` + + `${JSON.stringify(snapshot, null, 2)}\n` + + `${SNAPSHOT_END} */\n` + +const parseSnapshotFromText = (text) => { + const block = text.match( + new RegExp( + `${SNAPSHOT_BEGIN}\\s*([\\s\\S]*?)\\s*${SNAPSHOT_END}`, + "m" + ) + ) + if (!block) { + return { ok: false, error: "no_snapshot" } + } + try { + const parsed = JSON.parse(block[1].trim()) + const values = parsed.values ?? parsed + if (!values || typeof values !== "object" || Array.isArray(values)) { + return { ok: false, error: "invalid_format" } + } + return { ok: true, snapshot: { ...parsed, values } } + } catch (e) { + return { ok: false, error: "invalid_json", message: e.message } + } +} + +const snapshotImportErrorMessage = (parseResult) => { + if (!parseResult || parseResult.ok) return "" + switch (parseResult.error) { + case "no_snapshot": + return "No configurator snapshot in this file. Download configuration.h from this tool first (JSON block at end of file)." + case "invalid_json": + return parseResult.message + ? `Snapshot JSON is corrupted or invalid (${parseResult.message}).` + : "Snapshot JSON is corrupted or invalid." + case "invalid_format": + return "Snapshot format is invalid (missing values object)." + default: + return "Could not read configuration snapshot." + } +} + +const applyConfigurationSnapshot = (configuration, snapshot) => { + const values = snapshot.values ?? snapshot + const presetLike = { id: "import", values } + const { applied, unknown } = applyBoardPreset(configuration, presetLike) + return { + applied, + unknown, + fieldCount: Object.keys(values).length, + } +} + +export { + SNAPSHOT_BEGIN, + SNAPSHOT_END, + SNAPSHOT_VERSION, + collectConfigurationSnapshot, + formatSnapshotComment, + parseSnapshotFromText, + snapshotImportErrorMessage, + applyConfigurationSnapshot, +} diff --git a/src/configuration/esp3dCameraModels.js b/src/configuration/esp3dCameraModels.js new file mode 100644 index 0000000..6e2ff54 --- /dev/null +++ b/src/configuration/esp3dCameraModels.js @@ -0,0 +1,21 @@ +/** + * Camera models supported by ESP3D (from esp3d/src/include/esp3d_defines.h). + * Keep in sync with tmp/esp3d when updating firmware reference. + */ +const esp3dCameraModels = [ + { value: "CAMERA_MODEL_CUSTOM", label: "Custom", mcus: ["esp32", "esp32s2", "esp32s3"] }, + { value: "CAMERA_MODEL_ESP_EYE", label: "ESP-Eye", mcus: ["esp32"] }, + { value: "CAMERA_MODEL_M5STACK_PSRAM", label: "M5Stack", mcus: ["esp32"] }, + { value: "CAMERA_MODEL_M5STACK_V2_PSRAM", label: "M5Stack V2", mcus: ["esp32"] }, + { value: "CAMERA_MODEL_M5STACK_WIDE", label: "M5Stack Wide", mcus: ["esp32"] }, + { value: "CAMERA_MODEL_AI_THINKER", label: "AI Thinker (ESP32-Cam)", mcus: ["esp32"] }, + { value: "CAMERA_MODEL_WROVER_KIT", label: "Wrover Kit", mcus: ["esp32"] }, + { value: "CAMERA_MODEL_ESP32_CAM_BOARD", label: "Espressif ESP32 Cam Board", mcus: ["esp32"] }, + { value: "CAMERA_MODEL_ESP32S2_CAM_BOARD", label: "Espressif ESP32-S2 Cam Board", mcus: ["esp32s2"] }, + { value: "CAMERA_MODEL_ESP32S3_CAM_LCD", label: "Espressif ESP32-S3 Cam LCD", mcus: ["esp32s3"] }, + { value: "CAMERA_MODEL_ESP32S3_EYE", label: "ESP32-S3 Eye", mcus: ["esp32s3"] }, + { value: "CAMERA_MODEL_XIAO_ESP32S3", label: "Seeed Xiao ESP32-S3 Sense", mcus: ["esp32s3"] }, + { value: "CAMERA_MODEL_UICPAL_ESP32S3", label: "UICPAL ESP32-S3", mcus: ["esp32s3"] }, +] + +export { esp3dCameraModels } diff --git a/src/configuration/pinCapabilities.js b/src/configuration/pinCapabilities.js new file mode 100644 index 0000000..52d8c39 --- /dev/null +++ b/src/configuration/pinCapabilities.js @@ -0,0 +1,215 @@ +/** + * Per-MCU GPIO hardware capabilities (Espressif IDF restrictions + module wiring). + * Used to filter pin pickers and validate assignments against pinRole. + */ + +import { getPsramReservedGpios, needsPsram } from "./psramReservedPins" +import { getEffectivePinRole } from "./pinUsageRoles" + +/** @typedef {{ input: boolean, output: boolean, adc: boolean, reserved: boolean, note?: string }} GpioCapability */ + +const inRange = (gpio, from, to) => gpio >= from && gpio <= to +const inList = (gpio, list) => list.includes(gpio) + +/** Static rules per MCU (gpio 0–48). Dynamic PSRAM/SPI reserved merged in getGpioCapability. */ +const mcuRules = { + esp32: { + flashBus: [6, 7, 8, 9, 10, 11], + inputOnly: [34, 35, 36, 37, 38, 39], + adc: [32, 33, 34, 35, 36, 37, 39], + }, + esp8266: { + maxGpio: 16, + flashBus: [6, 7, 8, 9, 10, 11], + }, + esp8285: { + maxGpio: 16, + flashBus: [6, 7, 8, 9, 10, 11], + }, + esp32s2: { + spiBus: [26, 27, 28, 29, 30, 31], + inputOnly: [46], + }, + esp32s3: { + spiBus: [26, 27, 28, 29, 30, 31, 32], + octalExtra: [33, 34, 35, 36, 37], + inputOnly: [46], + }, + esp32c3: { + /** In-package flash on many C3 modules */ + flashBus: [11, 12, 13, 14, 15, 16], + }, + esp32c6: { + flashBus: [24, 25, 26, 27, 28, 29], + }, +} + +const isOctalPsramProfile = (memoryType) => + memoryType === "dio_opi" || + memoryType === "qio_opi" || + memoryType === "opi_opi" + +const buildStaticCapability = (mcu, gpio) => { + const rules = mcuRules[mcu] + if (!rules) { + return { input: true, output: true, adc: false, reserved: false } + } + + if (rules.maxGpio !== undefined && gpio > rules.maxGpio) { + return { + input: false, + output: false, + adc: false, + reserved: true, + note: "Not available on this MCU", + } + } + + let reserved = false + let note + + if (rules.flashBus && inList(gpio, rules.flashBus)) { + reserved = true + note = "SPI flash bus" + } + if (rules.spiBus && inList(gpio, rules.spiBus)) { + reserved = true + note = "SPI flash / PSRAM" + } + + const inputOnly = rules.inputOnly && inList(gpio, rules.inputOnly) + const hasAdc = rules.adc && inList(gpio, rules.adc) + + return { + input: true, + output: !inputOnly && !reserved, + adc: !!hasAdc, + reserved, + note, + } +} + +/** + * @param {string} mcu + * @param {number|string} gpio + * @param {Function} getValueId + * @returns {GpioCapability} + */ +const getGpioCapability = (mcu, gpio, getValueId) => { + if (gpio === "-1" || gpio === "" || gpio === undefined || gpio === null) { + return { input: true, output: true, adc: false, reserved: false } + } + const n = Number(gpio) + if (!Number.isFinite(n)) { + return { input: false, output: false, adc: false, reserved: true, note: "Invalid" } + } + + let cap = buildStaticCapability(mcu, n) + + if (needsPsram(getValueId)) { + if (mcu === "esp32" && inList(n, [16, 17])) { + cap = { ...cap, reserved: true, output: false, note: "PSRAM (module)" } + } + if ( + (mcu === "esp32s2" || mcu === "esp32s3") && + inRange(n, 26, 31) + ) { + cap = { ...cap, reserved: true, output: false, note: "SPI flash / PSRAM" } + } + if (mcu === "esp32s3" && inRange(n, 26, 32)) { + cap = { ...cap, reserved: true, output: false, note: "SPI flash / PSRAM" } + } + if ( + mcu === "esp32s3" && + isOctalPsramProfile(getValueId("memory_type")) && + inRange(n, 33, 37) + ) { + cap = { ...cap, reserved: true, output: false, note: "Octal flash / PSRAM" } + } + } + + const dynamicReserved = getPsramReservedGpios(getValueId) + if (dynamicReserved.includes(String(n))) { + cap = { ...cap, reserved: true, output: false, note: cap.note || "PSRAM / SPI bus" } + } + + return cap +} + +const roleAllowsCapability = (role, cap) => { + if (cap.reserved) return false + switch (role) { + case "output": + return cap.output + case "input": + return cap.input + case "bidirectional": + return cap.input && cap.output + case "analog_input": + return cap.input && cap.adc + case "optional_gpio": + return cap.input || cap.output + default: + return cap.input && cap.output + } +} + +const describeCapability = (cap) => { + if (cap.reserved) return cap.note || "Reserved" + if (cap.adc && !cap.output) return "Input / ADC only" + if (!cap.output) return "Input only" + if (!cap.input) return "Output only" + return "Input / Output" +} + +const rejectionMessage = (gpio, role, cap, mcu) => { + const gpioLabel = gpio === "-1" ? "Default" : `GPIO ${gpio}` + const roleLabel = role.replace(/_/g, " ") + if (cap.reserved) { + return `${gpioLabel} is reserved on ${mcu} (${cap.note || "module"})` + } + if (role === "analog_input" && !cap.adc) { + return `${gpioLabel} has no ADC on ${mcu}` + } + if (role === "output" && !cap.output) { + return `${gpioLabel} is input-only on ${mcu}` + } + if (role === "input" && !cap.input) { + return `${gpioLabel} is output-only on ${mcu}` + } + if (role === "bidirectional" && (!cap.input || !cap.output)) { + return `${gpioLabel} cannot be used as bidirectional on ${mcu} (${describeCapability(cap)})` + } + return `${gpioLabel} cannot be used as ${roleLabel} on ${mcu}` +} + +/** + * Whether a pin list option is allowed for this field. + */ +const isPinOptionAllowed = (field, optionValue, getValueId) => { + if (optionValue === "-1") return true + const role = getEffectivePinRole(field, getValueId) + if (!role) return true + const mcu = getValueId("targetmcu") + const cap = getGpioCapability(mcu, optionValue, getValueId) + return roleAllowsCapability(role, cap) +} + +const validatePinAssignment = (field, getValueId) => { + if (!field?.ispin && !field?.pinRole) return null + const role = getEffectivePinRole(field, getValueId) + if (!role || field.value == "-1" || field.value === -1) return null + const mcu = getValueId("targetmcu") + const cap = getGpioCapability(mcu, field.value, getValueId) + if (roleAllowsCapability(role, cap)) return null + return rejectionMessage(String(field.value), role, cap, mcu) +} + +export { + mcuRules, + getGpioCapability, + roleAllowsCapability, + describeCapability, + isPinOptionAllowed, + validatePinAssignment, +} diff --git a/src/configuration/pinConflicts.js b/src/configuration/pinConflicts.js index a164f51..6cc40e0 100644 --- a/src/configuration/pinConflicts.js +++ b/src/configuration/pinConflicts.js @@ -1,5 +1,10 @@ import { canShowField } from "./visibility" import { mcuPinDefaults, defaultPinRoles } from "./mcuPinDefaults" +import { + isSerialPinField, + resolveSerialEffectivePin, +} from "./serialPinDefaults" +import { psramPinUsages } from "./psramReservedPins" const forEachField = (configuration, getValueId, visitor) => { if (!configuration) return @@ -18,6 +23,9 @@ const forEachField = (configuration, getValueId, visitor) => { } const resolveEffectivePin = (field, getValueId) => { + if (isSerialPinField(field.id)) { + return resolveSerialEffectivePin(field, getValueId) + } if (!field.ispin) return null const raw = field.value if (raw != "-1") return String(raw) @@ -54,6 +62,7 @@ const collectPinUsages = (configuration, getValueId) => { description: describePinAssignment(field, effective), }) }) + usages.push(...psramPinUsages(getValueId)) return usages } diff --git a/src/configuration/pinUsageRoles.js b/src/configuration/pinUsageRoles.js new file mode 100644 index 0000000..a12cf07 --- /dev/null +++ b/src/configuration/pinUsageRoles.js @@ -0,0 +1,19 @@ +import rolesDoc from "./pinUsageRoles.json" + +const FIELD_ROLES = rolesDoc.taxonomy.fieldRoles +const REGISTRY = rolesDoc.fields + +/** + * Effective pinRole for a field (honours pinRoleWhen on the field object). + */ +const getEffectivePinRole = (field, getValueId) => { + if (!field?.pinRole) return null + const when = field.pinRoleWhen + if (when?.dependId && when.map && getValueId) { + const key = getValueId(when.dependId) + if (key && when.map[key]) return when.map[key] + } + return field.pinRole +} + +export { FIELD_ROLES, REGISTRY, getEffectivePinRole } diff --git a/src/configuration/pinUsageRoles.json b/src/configuration/pinUsageRoles.json new file mode 100644 index 0000000..b6e513f --- /dev/null +++ b/src/configuration/pinUsageRoles.json @@ -0,0 +1,115 @@ +{ + "_comment": "MCU-independent roles required by each config field. Hardware tags (input-only GPIO, PSRAM, …) come later per MCU.", + "taxonomy": { + "fieldRoles": { + "output": "Digital output (drive HIGH/LOW): CS, RST, LED, buzzer, UART TX, camera control, power enable.", + "input": "Digital input (read level): UART RX, card detect, IRQ, parallel camera sync/data in.", + "bidirectional": "Digital in+out on same line or bus bit: SPI MOSI/MISO, SDIO, MDIO, DHT one-wire, I2C SCCB (open-drain).", + "analog_input": "ADC reading only (e.g. analog sensor).", + "optional_gpio": "May be -1 (unused); when set, same rules as sibling role in notes." + }, + "hardwareTags": { + "_note": "Not field roles — applied per GPIO per MCU when building pin pickers.", + "reserved": "Flash / PSRAM / strapping / USB — never offered (see psramReservedPins.js).", + "input_only": "GPIO cannot be configured as output (ESP32: 34–39).", + "output_only": "GPIO cannot be configured as input (rare on ESP32).", + "adc": "Valid ADC channel when analog read is needed.", + "bidirectional": "Normal GPIO (input+output)." + }, + "userProposalMapping": { + "Any (Input/Output/ADC)": "Too loose for validation — use bidirectional or split by field.", + "Input/Output (Not ADC)": "≈ bidirectional (digital features).", + "Input only": "Hardware tag, not a field role — use with input fields.", + "Output only": "Hardware tag — use with output fields.", + "Input only ADC": "Hardware tag — required for analog_input fields.", + "Output only ADC": "Not used on ESP32 (no ADC output); use output or DAC later if needed.", + "Reserved": "hardwareTags.reserved — orthogonal to field roles." + }, + "recommendedMinimum": [ + "output", + "input", + "bidirectional", + "analog_input", + "reserved (hardware only)" + ] + }, + "fields": { + "ESP_RX_PIN": { "role": "input", "group": "serial", "label": "Main serial RX" }, + "ESP_TX_PIN": { "role": "output", "group": "serial", "label": "Main serial TX" }, + "ESP_BRIDGE_RX_PIN": { "role": "input", "group": "serial", "label": "Bridge serial RX" }, + "ESP_BRIDGE_TX_PIN": { "role": "output", "group": "serial", "label": "Bridge serial TX" }, + + "oledrstpin": { "role": "output", "group": "display", "label": "OLED reset", "notes": "optional -1" }, + "tftledpin": { "role": "output", "group": "display", "label": "TFT backlight" }, + "tftDCpin": { "role": "output", "group": "display", "label": "TFT DC" }, + "tftRSTpin": { "role": "output", "group": "display", "label": "TFT reset" }, + "tftCSpin": { "role": "output", "group": "display", "label": "TFT CS" }, + "tftMOSIpin": { "role": "bidirectional", "group": "display", "label": "TFT MOSI", "notes": "SPI master out; output-heavy" }, + "tftSCKpin": { "role": "output", "group": "display", "label": "TFT SCK" }, + + "buzzerpin": { "role": "output", "group": "devices", "label": "Buzzer" }, + "sensorpin": { + "role": "input", + "group": "devices", + "label": "Sensor data pin", + "roleWhen": { + "sensortype": { + "ANALOG_DEVICE": "analog_input", + "DHT11_DEVICE": "bidirectional", + "DHT22_DEVICE": "bidirectional" + } + } + }, + "resetpin": { "role": "input", "group": "update", "label": "Settings reset", "notes": "Held low at boot; input with pull-up" }, + + "ethernetpowerpin": { "role": "output", "group": "ethernet", "label": "PHY power" }, + "ethernetmdcpin": { "role": "output", "group": "ethernet", "label": "PHY MDC" }, + "ethernetmdiopin": { "role": "bidirectional", "group": "ethernet", "label": "PHY MDIO" }, + "ethernetspiirqpin": { "role": "input", "group": "ethernet", "label": "SPI PHY IRQ" }, + "ethernetspirstpin": { "role": "output", "group": "ethernet", "label": "SPI PHY reset" }, + "ethernetspicspin": { "role": "output", "group": "ethernet", "label": "SPI PHY CS" }, + "ethernetspimosipin": { "role": "bidirectional", "group": "ethernet", "label": "SPI PHY MOSI" }, + "ethernetspimisopin": { "role": "bidirectional", "group": "ethernet", "label": "SPI PHY MISO" }, + "ethernetspisckpin": { "role": "output", "group": "ethernet", "label": "SPI PHY SCK" }, + + "detectsdpin": { "role": "input", "group": "sd", "label": "SD card detect", "notes": "optional -1" }, + "shareflagsdpin": { "role": "output", "group": "sd", "label": "SD shared flag" }, + "sdcspin": { "role": "output", "group": "sd_spi", "label": "SD CS" }, + "sdmosipin": { "role": "bidirectional", "group": "sd_spi", "label": "SD MOSI" }, + "sdmisopin": { "role": "bidirectional", "group": "sd_spi", "label": "SD MISO" }, + "sdsckpin": { "role": "output", "group": "sd_spi", "label": "SD SCK" }, + "sdioclkpin": { "role": "output", "group": "sd_sdio", "label": "SDIO CLK" }, + "sdiocmdpin": { "role": "bidirectional", "group": "sd_sdio", "label": "SDIO CMD" }, + "sdiod0pin": { "role": "bidirectional", "group": "sd_sdio", "label": "SDIO D0" }, + "sdiod1pin": { "role": "bidirectional", "group": "sd_sdio", "label": "SDIO D1" }, + "sdiod2pin": { "role": "bidirectional", "group": "sd_sdio", "label": "SDIO D2" }, + "sdiod3pin": { "role": "bidirectional", "group": "sd_sdio", "label": "SDIO D3" }, + + "CAM_LED_PIN": { "role": "output", "group": "camera", "label": "Camera LED", "notes": "optional -1" }, + "CAM_PULLUP1": { "role": "optional_gpio", "group": "camera", "label": "Camera pull-up 1", "notes": "optional -1; when set treat as input or output per board" }, + "CAM_PULLUP2": { "role": "optional_gpio", "group": "camera", "label": "Camera pull-up 2", "notes": "optional -1" }, + "PWDN_GPIO_NUM": { "role": "output", "group": "camera", "label": "Camera power-down" }, + "RESET_GPIO_NUM": { "role": "output", "group": "camera", "label": "Camera reset", "notes": "optional -1" }, + "XCLK_GPIO_NUM": { "role": "output", "group": "camera", "label": "Camera XCLK" }, + "SIOD_GPIO_NUM": { "role": "bidirectional", "group": "camera", "label": "Camera I2C SDA", "notes": "open-drain" }, + "SIOC_GPIO_NUM": { "role": "bidirectional", "group": "camera", "label": "Camera I2C SCL", "notes": "open-drain" }, + "Y2_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y2" }, + "Y3_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y3" }, + "Y4_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y4" }, + "Y5_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y5" }, + "Y6_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y6" }, + "Y7_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y7" }, + "Y8_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y8" }, + "Y9_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP Y9" }, + "VSYNC_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP VSYNC" }, + "HREF_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP HREF" }, + "PCLK_GPIO_NUM": { "role": "input", "group": "camera", "label": "DVP PCLK" } + }, + "summaryByRole": { + "output": 22, + "input": 17, + "bidirectional": 13, + "optional_gpio": 2, + "analog_input": "sensorpin only when sensortype=ANALOG_DEVICE (see roleWhen)" + } +} diff --git a/src/configuration/psramReservedPins.js b/src/configuration/psramReservedPins.js new file mode 100644 index 0000000..3962145 --- /dev/null +++ b/src/configuration/psramReservedPins.js @@ -0,0 +1,57 @@ +/** + * GPIO lines tied to in-package / octal PSRAM (and SPI flash bus) per Espressif HW design. + * Used for pin pickers and conflict audit — not emitted in configuration.h. + * + * @see https://docs.espressif.com/projects/esp-idf/en/latest/esp32/api-reference/peripherals/gpio.html + */ + +const needsPsram = (getValueId) => + getValueId("has_psram") === true || + (getValueId("cameratype") && getValueId("cameratype") !== "-1") + +const isOctalPsramProfile = (memoryType) => + memoryType === "dio_opi" || + memoryType === "qio_opi" || + memoryType === "opi_opi" + +/** MCU → GPIO strings reserved when PSRAM is in use */ +const psramGpioByMcu = { + /** WROVER / modules with PSRAM: GPIO16 (CS), GPIO17 (SCLK) */ + esp32: ["16", "17"], + /** SPI flash + PSRAM on module (ESP32-S2) */ + esp32s2: ["26", "27", "28", "29", "30", "31"], + /** ESP32-S3R8 / octal variants: base SPI lines */ + esp32s3: ["26", "27", "28", "29", "30", "31", "32"], + esp32c3: ["26", "27", "28", "29", "30", "31", "32"], + esp32c6: ["26", "27", "28", "29", "30", "31", "32"], +} + +const octalExtraGpioEsp32s3 = ["33", "34", "35", "36", "37"] + +const getPsramReservedGpios = (getValueId) => { + if (!needsPsram(getValueId)) return [] + const mcu = getValueId("targetmcu") + const base = psramGpioByMcu[mcu] + if (!base) return [] + if (mcu === "esp32s3" && isOctalPsramProfile(getValueId("memory_type"))) { + return [...base, ...octalExtraGpioEsp32s3] + } + return [...base] +} + +const psramPinUsages = (getValueId) => + getPsramReservedGpios(getValueId).map((gpio) => ({ + section: "features", + id: "has_psram", + label: "PSRAM", + raw: "-1", + effective: gpio, + description: "Reserved by module PSRAM / SPI bus", + })) + +export { + needsPsram, + psramGpioByMcu, + getPsramReservedGpios, + psramPinUsages, +} diff --git a/src/configuration/reservedResources.js b/src/configuration/reservedResources.js index 7aaa8e8..43c115b 100644 --- a/src/configuration/reservedResources.js +++ b/src/configuration/reservedResources.js @@ -1,5 +1,6 @@ import { canShowField } from "./visibility" import { resolveEffectivePin } from "./pinConflicts" +import { getPsramReservedGpios } from "./psramReservedPins" const forEachField = (configuration, getValueId, visitor) => { if (!configuration) return @@ -44,6 +45,7 @@ const buildReservedLists = (configuration, getValueId, excludeFieldId) => { ports.push(String(field.value)) } }) + getPsramReservedGpios(getValueId).forEach((gpio) => pins.push(gpio)) if (ports.length === 0) { ports.push("USE_SERIAL_0") } diff --git a/src/configuration/serialPinDefaults.js b/src/configuration/serialPinDefaults.js new file mode 100644 index 0000000..7bff439 --- /dev/null +++ b/src/configuration/serialPinDefaults.js @@ -0,0 +1,163 @@ +/** + + * Representative UART default GPIO when ESP_*_PIN is -1 (Arduino core / common devkits). + + * Used only for UI labels and pin-conflict tracking, not firmware pin assignment. + + */ + + + +import { SERIAL_PIN_IDS } from "./serialPins" +import { needsPsram } from "./psramReservedPins" + + + +/** field id → config field that selects the UART */ + +const SERIAL_PORT_BY_FIELD = { + + ESP_RX_PIN: "serialport", + + ESP_TX_PIN: "serialport", + + ESP_BRIDGE_RX_PIN: "serialBridgeport", + + ESP_BRIDGE_TX_PIN: "serialBridgeport", + +} + + + +/** + + * MCU → USE_SERIAL_* → { rx, tx } (omit rx/tx when UART has no default on that line) + + */ + +const uartDefaultGpio = { + + esp32: { + + USE_SERIAL_0: { rx: 3, tx: 1 }, + + USE_SERIAL_1: { rx: 9, tx: 10 }, + + USE_SERIAL_2: { rx: 16, tx: 17 }, + + }, + + esp32s2: { + + USE_SERIAL_0: { rx: 44, tx: 43 }, + + USE_SERIAL_1: { rx: 18, tx: 17 }, + + }, + + esp32s3: { + + USE_SERIAL_0: { rx: 44, tx: 43 }, + + USE_SERIAL_1: { rx: 18, tx: 17 }, + + USE_SERIAL_2: { rx: 8, tx: 9 }, + + }, + + esp32c3: { + + USE_SERIAL_0: { rx: 20, tx: 21 }, + + USE_SERIAL_1: { rx: 18, tx: 19 }, + + }, + + esp32c6: { + + USE_SERIAL_0: { rx: 20, tx: 21 }, + + USE_SERIAL_1: { rx: 18, tx: 19 }, + + }, + + esp8266: { + + USE_SERIAL_0: { rx: 3, tx: 1 }, + + USE_SERIAL_1: { tx: 2 }, + + }, + + esp8285: { + + USE_SERIAL_0: { rx: 3, tx: 1 }, + + USE_SERIAL_1: { tx: 2 }, + + }, + +} + + + +const isSerialPinField = (fieldId) => SERIAL_PIN_IDS.includes(fieldId) + + + +const serialPinRole = (fieldId) => + + fieldId.includes("_RX_") || fieldId.endsWith("_RX_PIN") ? "rx" : "tx" + + + +const resolveSerialEffectivePin = (field, getValueId) => { + + if (!isSerialPinField(field.id)) return null + + if (field.value != "-1" && field.value != -1) { + + return String(field.value) + + } + + const portField = SERIAL_PORT_BY_FIELD[field.id] + + const port = getValueId(portField) + + if (!port || port === "-1") return null + + const mcu = getValueId("targetmcu") + + const role = serialPinRole(field.id) + + const uart = uartDefaultGpio[mcu]?.[port] + + if (!uart || uart[role] === undefined) return null + const gpio = uart[role] + if ( + mcu === "esp32" && + needsPsram(getValueId) && + (gpio === 16 || gpio === 17) + ) { + return null + } + return String(gpio) + +} + + + +export { + + SERIAL_PORT_BY_FIELD, + + uartDefaultGpio, + + isSerialPinField, + + resolveSerialEffectivePin, + +} + + diff --git a/src/configuration/serialPins.js b/src/configuration/serialPins.js new file mode 100644 index 0000000..5edf960 --- /dev/null +++ b/src/configuration/serialPins.js @@ -0,0 +1,69 @@ +/** + * Serial GPIO overrides (ESP3D esp3d_pins.h). + * -1 = board default for the UART selected in ESP_SERIAL_OUTPUT / bridge port. + */ + +const MAIN_SERIAL_PINS = [ + { id: "ESP_RX_PIN", label: "Main serial RX" }, + { id: "ESP_TX_PIN", label: "Main serial TX" }, +] + +const BRIDGE_SERIAL_PINS = [ + { id: "ESP_BRIDGE_RX_PIN", label: "Bridge serial RX" }, + { id: "ESP_BRIDGE_TX_PIN", label: "Bridge serial TX" }, +] + +const SERIAL_PIN_IDS = [ + ...MAIN_SERIAL_PINS.map((p) => p.id), + ...BRIDGE_SERIAL_PINS.map((p) => p.id), +] + +const emitPinOverride = (id, val) => + `#ifndef ${id}\n#define ${id} ${val}\n#endif // ${id}\n` + +const collectPinOverrides = (pins, getValueId) => { + let lines = "" + pins.forEach(({ id }) => { + const val = getValueId(id) + if (val === undefined || val === "" || val == -1) return + lines += emitPinOverride(id, val) + }) + return lines +} + +const formatSerialPinDefines = (getValueId) => { + const bridgeActive = getValueId("serialBridge") === true + + let block = + "\n/************************************\n" + + "*\n* Serial GPIO overrides (main + bridge)\n" + + "*\n* -1 = board default (not emitted; see esp3d_pins.h)\n" + + "*\n************************************/\n" + + const mainLines = collectPinOverrides(MAIN_SERIAL_PINS, getValueId) + const bridgeLines = bridgeActive + ? collectPinOverrides(BRIDGE_SERIAL_PINS, getValueId) + : "" + + if (!mainLines && !bridgeLines) return "" + + if (mainLines) block += mainLines + + if (bridgeLines) { + block += + "#if defined(ESP_SERIAL_BRIDGE_OUTPUT)\n" + + bridgeLines + + "#endif // ESP_SERIAL_BRIDGE_OUTPUT\n" + } + + return block +} + +export { + MAIN_SERIAL_PINS, + BRIDGE_SERIAL_PINS, + SERIAL_PIN_IDS, + emitPinOverride, + formatSerialPinDefines, +} + \ No newline at end of file diff --git a/src/configuration/tabs/devices.json b/src/configuration/tabs/devices.json index a72d962..343afaa 100644 --- a/src/configuration/tabs/devices.json +++ b/src/configuration/tabs/devices.json @@ -108,7 +108,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "tftledpin", @@ -126,7 +127,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "tftDCpin", @@ -144,7 +146,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "tftRSTpin", @@ -162,7 +165,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "tftCSpin", @@ -181,7 +185,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "tftMOSIpin", @@ -200,7 +205,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "tftSCKpin", @@ -219,7 +225,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" } ] }, @@ -255,7 +262,8 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" } ] }, @@ -338,7 +346,16 @@ ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "input", + "pinRoleWhen": { + "dependId": "sensortype", + "map": { + "ANALOG_DEVICE": "analog_input", + "DHT11_DEVICE": "bidirectional", + "DHT22_DEVICE": "bidirectional" + } + } }, { "id": "analogsensorconverter", @@ -395,7 +412,7 @@ }, { "id": "camera", - "description": "Connected camera", + "description": "Connected camera — only boards with PSRAM are supported. Enable Has PSRAM on Features first.", "label": "Camera settings", "setting": true, "depend": [ @@ -486,6 +503,16 @@ ] } }, + { + "label": "M5Stack Wide", + "value": "CAMERA_MODEL_M5STACK_WIDE", + "depend": { + "id": "targetmcu", + "value": [ + "esp32" + ] + } + }, { "label": "Wrover Kit", "value": "CAMERA_MODEL_WROVER_KIT", @@ -535,6 +562,29 @@ "esp32s3" ] } + }, + { + "label": "UICPAL ESP32-S3", + "value": "CAMERA_MODEL_UICPAL_ESP32S3", + "depend": { + "id": "targetmcu", + "value": [ + "esp32s3" + ] + } + }, + { + "label": "Custom", + "value": "CAMERA_MODEL_CUSTOM", + "help": "Edit GPIO below; values are written to configuration.h", + "depend": { + "id": "targetmcu", + "value": [ + "esp32", + "esp32s2", + "esp32s3" + ] + } } ] }, @@ -571,5 +621,351 @@ "setting": true } ] + }, + { + "id": "camerapins", + "label": "Camera GPIO map", + "description": "Pin map for the selected camera model (read-only unless Custom). Custom pins use the same GPIO filters as other features; -1 = unused.", + "type": "group", + "setting": false, + "depend": [ + { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + { + "id": "has_psram", + "value": [ + true + ] + } + ], + "value": [ + { + "id": "CAM_LED_PIN", + "define": "CAM_LED_PIN", + "label": "CAM_LED_PIN", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "4", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "output", + "ispin": true + }, + { + "id": "CAM_PULLUP1", + "define": "CAM_PULLUP1", + "label": "CAM_PULLUP1", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "-1", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "optional_gpio", + "ispin": true + }, + { + "id": "CAM_PULLUP2", + "define": "CAM_PULLUP2", + "label": "CAM_PULLUP2", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "-1", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "optional_gpio", + "ispin": true + }, + { + "id": "PWDN_GPIO_NUM", + "define": "PWDN_GPIO_NUM", + "label": "PWDN_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "32", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "output", + "ispin": true + }, + { + "id": "RESET_GPIO_NUM", + "define": "RESET_GPIO_NUM", + "label": "RESET_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "-1", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "output", + "ispin": true + }, + { + "id": "XCLK_GPIO_NUM", + "define": "XCLK_GPIO_NUM", + "label": "XCLK_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "0", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "output", + "ispin": true + }, + { + "id": "SIOD_GPIO_NUM", + "define": "SIOD_GPIO_NUM", + "label": "SIOD_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "26", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "bidirectional", + "ispin": true + }, + { + "id": "SIOC_GPIO_NUM", + "define": "SIOC_GPIO_NUM", + "label": "SIOC_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "27", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "bidirectional", + "ispin": true + }, + { + "id": "Y9_GPIO_NUM", + "define": "Y9_GPIO_NUM", + "label": "Y9_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "35", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "Y8_GPIO_NUM", + "define": "Y8_GPIO_NUM", + "label": "Y8_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "34", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "Y7_GPIO_NUM", + "define": "Y7_GPIO_NUM", + "label": "Y7_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "39", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "Y6_GPIO_NUM", + "define": "Y6_GPIO_NUM", + "label": "Y6_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "36", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "Y5_GPIO_NUM", + "define": "Y5_GPIO_NUM", + "label": "Y5_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "21", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "Y4_GPIO_NUM", + "define": "Y4_GPIO_NUM", + "label": "Y4_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "19", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "Y3_GPIO_NUM", + "define": "Y3_GPIO_NUM", + "label": "Y3_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "18", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "Y2_GPIO_NUM", + "define": "Y2_GPIO_NUM", + "label": "Y2_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "5", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "VSYNC_GPIO_NUM", + "define": "VSYNC_GPIO_NUM", + "label": "VSYNC_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "25", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "HREF_GPIO_NUM", + "define": "HREF_GPIO_NUM", + "label": "HREF_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "23", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + }, + { + "id": "PCLK_GPIO_NUM", + "define": "PCLK_GPIO_NUM", + "label": "PCLK_GPIO_NUM", + "description": "Camera GPIO (-1 = unused)", + "type": "select", + "value": "22", + "setting": false, + "depend": { + "id": "cameratype", + "notvalue": [ + "-1" + ] + }, + "pinRole": "input", + "ispin": true + } + ] } ] diff --git a/src/configuration/tabs/features.json b/src/configuration/tabs/features.json index 32c1b39..49cef23 100644 --- a/src/configuration/tabs/features.json +++ b/src/configuration/tabs/features.json @@ -4,6 +4,7 @@ "description": "ESP Board using ESP3D firmware", "label": "Board description", "setting": false, + "skipExport": true, "type": "group", "value": [ { @@ -208,7 +209,9 @@ { "id": "has_psram", "define": "BOARD_HAS_PSRAM", - "description": "Has External PSRAM", + "description": "Enable if your module includes PSRAM (required for camera).", + "footnote": "With PSRAM enabled: ESP32 (WROVER) reserves GPIO 16–17; ESP32-S3 reserves GPIO 26–32 (and 33–37 with octal PSRAM). These GPIO are tracked for conflicts.", + "footnoteWhenActive": true, "label": "Has PSRAM", "type": "boolean", "depend": { @@ -224,10 +227,11 @@ { "id": "memory_type", "define": "board_build.arduino.memory_type", - "description": "Memory type", + "description": "Flash and PSRAM bus mode for platformio.ini (ESP32-S3 modules). Must match your board (e.g. N8R2 vs N16R8, octal PSRAM).", "label": "Memory type", "type": "select", "value": "qio_qspi", + "footnote": "Please note: if the board fails to boot, this setting may not match your module and must be adjusted accordingly.", "depend": [ { "id": "targetmcu", @@ -295,13 +299,37 @@ { "id": "serialport", "define": "ESP_SERIAL_OUTPUT", - "description": "Which serial port ESP3D use to communicate with printer or TFT", - "label": "Serial port", + "description": "UART used for printer or TFT (main serial)", + "label": "Main serial port", "type": "select", "value": "USE_SERIAL_0", "setting": true, "isport": true }, + { + "id": "ESP_RX_PIN", + "define": "ESP_RX_PIN", + "label": "RX", + "description": "Default (-1) uses board UART pins for the selected main serial port", + "type": "select", + "value": "-1", + "setting": false, + "ispin": true, + "usedefault": true, + "pinRole": "input" + }, + { + "id": "ESP_TX_PIN", + "define": "ESP_TX_PIN", + "label": "TX", + "description": "Default (-1) uses board UART pins for the selected main serial port", + "type": "select", + "value": "-1", + "setting": false, + "ispin": true, + "usedefault": true, + "pinRole": "output" + }, { "id": "serialbuffer", "define": "SERIAL_RX_BUFFER_SIZE", @@ -366,13 +394,12 @@ }, "value": false, "disableiffalse": true, - "hide": true, - "setting": false + "setting": true }, { "id": "serialBridgeport", "define": "ESP_SERIAL_BRIDGE_OUTPUT", - "description": "Which serial port serial bridge to output to", + "description": "Second UART for serial bridge (must differ from main serial port when possible)", "depend": { "id": "serialBridge", "value": [ @@ -381,9 +408,63 @@ }, "label": "Serial bridge port", "type": "select", - "value": "-1", + "value": "USE_SERIAL_1", "setting": true, "isport": true + }, + { + "id": "ESP_BRIDGE_RX_PIN", + "define": "ESP_BRIDGE_RX_PIN", + "label": "RX", + "description": "Default (-1) uses board UART pins for the selected bridge port", + "type": "select", + "value": "-1", + "setting": false, + "ispin": true, + "usedefault": true, + "depend": [ + { + "id": "serialBridge", + "value": [ + true + ] + }, + { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + } + ], + "pinRole": "input" + }, + { + "id": "ESP_BRIDGE_TX_PIN", + "define": "ESP_BRIDGE_TX_PIN", + "label": "TX", + "description": "Default (-1) uses board UART pins for the selected bridge port", + "type": "select", + "value": "-1", + "setting": false, + "ispin": true, + "usedefault": true, + "depend": [ + { + "id": "serialBridge", + "value": [ + true + ] + }, + { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + } + ], + "pinRole": "output" } ] }, diff --git a/src/configuration/tabs/filesystems.json b/src/configuration/tabs/filesystems.json index 7320ae3..ccc0a5f 100644 --- a/src/configuration/tabs/filesystems.json +++ b/src/configuration/tabs/filesystems.json @@ -26,7 +26,12 @@ "help": "Only on ESP32 with large flash", "depend": { "id": "targetmcu", - "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] + "value": [ + "esp32", + "esp32s2", + "esp32s3", + "esp32c3" + ] } } ] @@ -49,7 +54,12 @@ "label": "SD filesystem", "setting": true, "type": "group", - "depend": { "id": "formfactor", "notvalue": ["esp01"] }, + "depend": { + "id": "formfactor", + "notvalue": [ + "esp01" + ] + }, "value": [ { "id": "sdconnection", @@ -87,7 +97,10 @@ "value": "ESP_SDFAT2", "depend": { "id": "sdconnection", - "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] + "value": [ + "ESP_SHARED_SD", + "ESP_NOT_SHARED_SD" + ] }, "setting": true, "options": [ @@ -107,7 +120,12 @@ "help": "Used by SDMMC", "depend": { "id": "targetmcu", - "value": ["esp32", "esp32s2", "esp32s3", "esp32c3"] + "value": [ + "esp32", + "esp32s2", + "esp32s3", + "esp32c3" + ] } } ] @@ -119,7 +137,12 @@ "description": "Mode used by SDIO library", "type": "select", "value": "SD_FOUR_BIT_MODE", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "depend": { + "id": "sdfs", + "value": [ + "ESP_SDIO" + ] + }, "setting": true, "options": [ { @@ -152,7 +175,10 @@ "type": "group", "depend": { "id": "sdconnection", - "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] + "value": [ + "ESP_SHARED_SD", + "ESP_NOT_SHARED_SD" + ] }, "value": [ { @@ -165,10 +191,14 @@ "value": "-1", "depend": { "id": "sdconnection", - "value": ["ESP_SHARED_SD", "ESP_NOT_SHARED_SD"] + "value": [ + "ESP_SHARED_SD", + "ESP_NOT_SHARED_SD" + ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "input" }, { "id": "sddetectvalue", @@ -177,7 +207,12 @@ "description": "State of SD card detect pin when card is present", "type": "select", "value": "0", - "depend": { "id": "detectsdpin", "notvalue": ["-1"] }, + "depend": { + "id": "detectsdpin", + "notvalue": [ + "-1" + ] + }, "setting": true, "usedescforoptions": true, "options": [ @@ -201,10 +236,13 @@ "value": "-1", "depend": { "id": "sdconnection", - "value": ["ESP_SHARED_SD"] + "value": [ + "ESP_SHARED_SD" + ] }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "shareflagsdvalue", @@ -213,7 +251,12 @@ "description": "State of SD card share pin for ESP board", "type": "select", "value": "0", - "depend": { "id": "shareflagsdpin", "notvalue": ["-1"] }, + "depend": { + "id": "shareflagsdpin", + "notvalue": [ + "-1" + ] + }, "setting": true, "usedescforoptions": true, "options": [ @@ -236,9 +279,15 @@ "ispin": true, "usedefault": true, "value": "-1", - "depend": { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, + "depend": { + "id": "sdfs", + "notvalue": [ + "ESP_SDIO" + ] + }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "sdmosipin", @@ -250,11 +299,23 @@ "usedefault": true, "value": "-1", "depend": [ - { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, - { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } + { + "id": "sdfs", + "notvalue": [ + "ESP_SDIO" + ] + }, + { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "sdmisopin", @@ -266,11 +327,23 @@ "usedefault": true, "value": "-1", "depend": [ - { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, - { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } + { + "id": "sdfs", + "notvalue": [ + "ESP_SDIO" + ] + }, + { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "sdsckpin", @@ -282,11 +355,23 @@ "usedefault": true, "value": "-1", "depend": [ - { "id": "sdfs", "notvalue": ["ESP_SDIO"] }, - { "id": "targetmcu", "notvalue": ["esp8266", "esp8285"] } + { + "id": "sdfs", + "notvalue": [ + "ESP_SDIO" + ] + }, + { + "id": "targetmcu", + "notvalue": [ + "esp8266", + "esp8285" + ] + } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "sdioclkpin", @@ -297,9 +382,15 @@ "ispin": true, "usedefault": true, "value": "-1", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "depend": { + "id": "sdfs", + "value": [ + "ESP_SDIO" + ] + }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "sdiocmdpin", @@ -310,9 +401,15 @@ "ispin": true, "usedefault": true, "value": "-1", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "depend": { + "id": "sdfs", + "value": [ + "ESP_SDIO" + ] + }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "sdiod0pin", @@ -323,9 +420,15 @@ "ispin": true, "usedefault": true, "value": "-1", - "depend": { "id": "sdfs", "value": ["ESP_SDIO"] }, + "depend": { + "id": "sdfs", + "value": [ + "ESP_SDIO" + ] + }, "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "sdiod1pin", @@ -337,11 +440,22 @@ "usedefault": true, "value": "-1", "depend": [ - { "id": "sdfs", "value": ["ESP_SDIO"] }, - { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } + { + "id": "sdfs", + "value": [ + "ESP_SDIO" + ] + }, + { + "id": "sdio", + "value": [ + "SD_FOUR_BIT_MODE" + ] + } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "sdiod2pin", @@ -353,11 +467,22 @@ "usedefault": true, "value": "-1", "depend": [ - { "id": "sdfs", "value": ["ESP_SDIO"] }, - { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } + { + "id": "sdfs", + "value": [ + "ESP_SDIO" + ] + }, + { + "id": "sdio", + "value": [ + "SD_FOUR_BIT_MODE" + ] + } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "sdiod3pin", @@ -369,11 +494,22 @@ "usedefault": true, "value": "-1", "depend": [ - { "id": "sdfs", "value": ["ESP_SDIO"] }, - { "id": "sdio", "value": ["SD_FOUR_BIT_MODE"] } + { + "id": "sdfs", + "value": [ + "ESP_SDIO" + ] + }, + { + "id": "sdio", + "value": [ + "SD_FOUR_BIT_MODE" + ] + } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" } ] }, @@ -391,7 +527,12 @@ "description": "Allows to access to all filesystems fromsame location", "type": "boolean", "value": false, - "depend": { "id": "sdconnection", "notvalue": ["-1"] }, + "depend": { + "id": "sdconnection", + "notvalue": [ + "-1" + ] + }, "disableiffalse": true, "setting": true }, @@ -412,7 +553,12 @@ { "label": "All filesystems", "value": "FS_ROOT", - "depend": { "id": "globalFS", "value": [true] } + "depend": { + "id": "globalFS", + "value": [ + true + ] + } }, { "label": "ESP3D Flash", @@ -423,7 +569,9 @@ "value": "FS_SD", "depend": { "id": "sdconnection", - "notvalue": ["-1"] + "notvalue": [ + "-1" + ] } } ] @@ -445,7 +593,12 @@ { "label": "All filesystems", "value": "FS_ROOT", - "depend": { "id": "globalFS", "value": [true] } + "depend": { + "id": "globalFS", + "value": [ + true + ] + } }, { "label": "ESP3D Flash", @@ -456,7 +609,9 @@ "value": "FS_SD", "depend": { "id": "sdconnection", - "notvalue": ["-1"] + "notvalue": [ + "-1" + ] } } ] diff --git a/src/configuration/tabs/network.json b/src/configuration/tabs/network.json index da80ce1..d478866 100644 --- a/src/configuration/tabs/network.json +++ b/src/configuration/tabs/network.json @@ -293,7 +293,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "ethernetmdcpin", @@ -315,7 +316,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "ethernetmdiopin", @@ -337,7 +339,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "ethernetspiirqpin", @@ -359,7 +362,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "input" }, { "id": "ethernetspirstpin", @@ -381,7 +385,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "ethernetspi", @@ -433,7 +438,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "ethernetspimosipin", @@ -455,7 +461,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "ethernetspimisopin", @@ -477,7 +484,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "bidirectional" }, { "id": "ethernetspisckpin", @@ -499,7 +507,8 @@ } ], "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "output" }, { "id": "bluetooth", diff --git a/src/configuration/tabs/update.json b/src/configuration/tabs/update.json index 41e464c..b01f330 100644 --- a/src/configuration/tabs/update.json +++ b/src/configuration/tabs/update.json @@ -25,10 +25,16 @@ "type": "select", "ispin": true, "header": "#define PIN_RESET_FEATURE\n", - "depend": { "id": "resetfeature", "value": [true] }, + "depend": { + "id": "resetfeature", + "value": [ + true + ] + }, "value": "-1", "setting": true, - "usedescforoptions": true + "usedescforoptions": true, + "pinRole": "input" } ] }, @@ -37,7 +43,13 @@ "description": "Update ESP3D firmware", "label": "Update ESP3D", "setting": true, - "depend": { "id": "targetflashsize", "notvalue": ["1", "2"] }, + "depend": { + "id": "targetflashsize", + "notvalue": [ + "1", + "2" + ] + }, "type": "group", "value": [ { @@ -66,7 +78,12 @@ "label": "Enable SD card Update", "description": "Update firmware and settings using file on SDCard", "type": "boolean", - "depend": { "id": "sdconnection", "notvalue": ["-1"] }, + "depend": { + "id": "sdconnection", + "notvalue": [ + "-1" + ] + }, "value": true, "disableiffalse": true, "setting": true diff --git a/src/configuration/validateField.js b/src/configuration/validateField.js index 681322a..0b6bd0c 100644 --- a/src/configuration/validateField.js +++ b/src/configuration/validateField.js @@ -1,6 +1,7 @@ import { canShowField } from "./visibility" import { findDuplicateValues, buildReservedLists } from "./reservedResources" import { auditPinConflicts, resolveEffectivePin } from "./pinConflicts" +import { validatePinAssignment } from "./pinCapabilities" const validateField = (field, getValueId, configuration) => { const validation = { @@ -11,6 +12,43 @@ const validateField = (field, getValueId, configuration) => { if (!field || !canShowField(field.depend, getValueId)) { return validation } + if (configuration && field.ispin) { + const pinRoleMsg = validatePinAssignment(field, getValueId) + if (pinRoleMsg) { + return { + message: pinRoleMsg, + valid: false, + modified: true, + } + } + const effective = resolveEffectivePin(field, getValueId) + if (effective) { + const capMsg = validatePinAssignment( + { ...field, value: effective }, + getValueId + ) + if (capMsg) { + return { + message: `Default resolves to ${capMsg}`, + valid: false, + modified: true, + } + } + const { conflicts } = auditPinConflicts(configuration, getValueId) + const conflict = conflicts.find((c) => c.gpio === effective) + if (conflict) { + const others = conflict.entries.filter((e) => e.id !== field.id) + if (others.length) { + return { + message: `GPIO ${effective} conflicts with ${others[0].label} (${others[0].description})`, + valid: false, + modified: true, + } + } + } + } + } + if (!field.setting) { return validation } @@ -40,23 +78,6 @@ const validateField = (field, getValueId, configuration) => { } } if (configuration) { - if (field.ispin) { - const effective = resolveEffectivePin(field, getValueId) - if (effective) { - const { conflicts } = auditPinConflicts(configuration, getValueId) - const conflict = conflicts.find((c) => c.gpio === effective) - if (conflict) { - const others = conflict.entries.filter((e) => e.id !== field.id) - if (others.length) { - return { - message: `GPIO ${effective} conflicts with ${others[0].label} (${others[0].description})`, - valid: false, - modified: true, - } - } - } - } - } if (field.isport && field.value != "-1") { const { ports } = buildReservedLists(configuration, getValueId, field.id) const duplicates = findDuplicateValues(ports) diff --git a/src/contexts/DatasContext.js b/src/contexts/DatasContext.js index cd30a08..c2d3b6d 100644 --- a/src/contexts/DatasContext.js +++ b/src/contexts/DatasContext.js @@ -19,6 +19,7 @@ import { h, createContext } from "preact" import { useRef, useContext, useState } from "preact/hooks" import defaultConfiguration from "../configuration" +import { applyConfigurationSnapshot } from "../configuration/configurationSnapshot" /* * Local const @@ -96,8 +97,21 @@ const DatasContextProvider = ({ children }) => { } useDatasContextFn.getValue = getValue useDatasContextFn.getValueId = getValueId + + const [configRevision, setConfigRevision] = useState(0) + const importConfigurationSnapshot = (snapshot) => { + const result = applyConfigurationSnapshot( + configuration.current, + snapshot + ) + setConfigRevision((revision) => revision + 1) + return result + } + const store = { configuration, + configRevision, + importConfigurationSnapshot, } return ( diff --git a/src/contexts/UiContext.js b/src/contexts/UiContext.js index 767d448..1d99e0c 100644 --- a/src/contexts/UiContext.js +++ b/src/contexts/UiContext.js @@ -49,16 +49,10 @@ const UiContextProvider = ({ children }) => { ":" + now.getSeconds().toString().padStart(2, "0") - setToasts([...toastsRef.current, { ...newToast, id }]) - setNotifications([ - ...notificationsRef.current, - { ...newToast, id, time }, - ]) + setToasts([...toastsRef.current, { ...newToast, id, time }]) } - const clearNotifications = () => { - setNotifications([]) - } + const clearNotifications = () => {} const removeToast = (uids) => { const removedIds = removeEntriesByIDs(toastsRef.current, uids) @@ -91,6 +85,7 @@ const UiContextProvider = ({ children }) => { useUiContextFn.getElement = (v) => { console.log(v, "not found") } + useUiContextFn.haptic = () => {} useUiContextFn.toasts = { addToast, removeToast, toastList: toasts } diff --git a/src/index.html b/src/index.html index c018e07..ef7ce60 100644 --- a/src/index.html +++ b/src/index.html @@ -5,6 +5,23 @@ ESP3D-Configurator + diff --git a/src/index.js b/src/index.js index e21cd5f..6b6b035 100644 --- a/src/index.js +++ b/src/index.js @@ -20,7 +20,10 @@ if (process.env.NODE_ENV === "development") { require("preact/debug") } +import { initTheme } from "./theme/theme" import { h, render } from "preact" import { App } from "./components/App" import "./style/index.scss" + +initTheme() render(, document.body) diff --git a/src/style/_theme.scss b/src/style/_theme.scss new file mode 100644 index 0000000..e5e6006 --- /dev/null +++ b/src/style/_theme.scss @@ -0,0 +1,157 @@ +// UI theme tokens (light defaults on :root; dark overrides on [data-theme="dark"]) + +:root { + color-scheme: light; + --th-bg-page: #{$bg-color}; + --th-bg-surface: #ffffff; + --th-bg-muted: #f1f1fc; + --th-bg-navbar: #{$bg-color-dark}; + --th-text: #{$body-font-color}; + --th-text-muted: #{$gray-color}; + --th-primary: #{$primary-color}; + --th-link: blue; + --th-border: #dadee4; + --th-input-bg: #ffffff; + --th-shadow: rgba(0, 0, 0, 0.05); +} + +[data-theme="dark"] { + color-scheme: dark; + --th-bg-page: #1a1d23; + --th-bg-surface: #252930; + --th-bg-muted: #2e3340; + --th-bg-navbar: #12151a; + --th-text: #e4e6eb; + --th-text-muted: #a8adb8; + --th-primary: #8b89ff; + --th-link: #9d9bff; + --th-border: #3d4452; + --th-input-bg: #2a2f3a; + --th-shadow: rgba(0, 0, 0, 0.35); +} + +html, +body, +#app { + background-color: var(--th-bg-page); + color: var(--th-text); +} + +.navbar { + background-color: var(--th-bg-navbar) !important; +} + +.panel .navbar, +.panel .navbar-section { + background: var(--th-bg-muted) !important; + color: var(--th-primary) !important; +} + +.panel .tab-item, +.field-group, +.tab-item { + color: var(--th-primary) !important; +} + +section .active, +li .active { + color: var(--th-link) !important; +} + +.mobile-view section .active, +.mobile-view li .active { + background-color: rgba(139, 137, 255, 0.15) !important; +} + +.form-input, +.form-select, +textarea.form-input { + background-color: var(--th-input-bg); + color: var(--th-text); + border-color: var(--th-border); +} + +.form-label, +.text-dark { + color: var(--th-text) !important; +} + +.fieldset-bottom-separator, +.fieldset-top-separator, +.itemEditor, +.files-list { + border-color: var(--th-border); +} + +.btn-link, +.feather-icon-container { + color: var(--th-primary); +} + +.btn-header, +.item-selection-list:hover, +.btn-header { + background-color: var(--th-bg-muted); +} + +.column .panel { + box-shadow: 0 0 14px -5px var(--th-shadow); +} + +.modal-container .modal, +.toast { + background: var(--th-bg-surface); + color: var(--th-text); +} + +// Spectre sets colored backgrounds on .toast-success / .toast-error; keep surface +// background so message text (green/red) stays readable. +.toasts-container .toast.toast-success, +.toasts-container .toast.toast-error { + background: var(--th-bg-surface) !important; + border: 0.05rem solid var(--th-border) !important; + color: var(--th-text) !important; + min-width: 12rem; +} + +.toasts-container .toast.toast-success { + border-left: 0.25rem solid $success-color !important; + + .toast-message { + color: $success-color !important; + } +} + +.toasts-container .toast.toast-error { + border-left: 0.25rem solid $error-color !important; + + .toast-message { + color: $error-color !important; + } +} + +.toasts-container .toast .toast-message { + display: block; + padding-right: 1.75rem; +} + +.board-preset-intro { + color: var(--th-text-muted); +} + +fieldset.board-preset-picker.field-group { + p.board-preset-import-feedback--success { + color: $success-color !important; + } + + p.board-preset-import-feedback--error { + color: $error-color !important; + } +} + +[data-theme="dark"] .accordion-header, +[data-theme="dark"] pre, +[data-theme="dark"] code { + background: var(--th-bg-muted); + color: var(--th-text); +} diff --git a/src/style/components/_control.scss b/src/style/components/_control.scss index 5c3bf9e..7966d0f 100644 --- a/src/style/components/_control.scss +++ b/src/style/components/_control.scss @@ -371,6 +371,59 @@ a label { width: fit-content; } +.field-footnote { + display: flex; + align-items: flex-start; + gap: 0.4rem; + color: $primary-color; + + .field-footnote-icon { + flex-shrink: 0; + margin-top: 0.15rem; + color: $primary-color; + stroke: $primary-color; + } +} + +.board-preset-picker { + max-width: fit-content; + margin-left: auto; + margin-right: auto; + + .board-preset-intro { + max-width: 28rem; + text-align: left; + color: $body-font-color; + } + + .field-group-content { + align-items: flex-start; + } + + .board-preset-row { + display: flex; + flex-wrap: wrap; + align-items: flex-end; + gap: 0.5rem; + } + + .board-preset-import-feedback { + max-width: 28rem; + text-align: left; + font-size: 0.9rem; + margin-top: 0.25rem; + font-weight: 500; + + &--success { + color: $success-color !important; + } + + &--error { + color: $error-color !important; + } + } +} + .has-modification svg { color: $warning-color; } diff --git a/src/style/components/_theme-toggle.scss b/src/style/components/_theme-toggle.scss new file mode 100644 index 0000000..deba7fd --- /dev/null +++ b/src/style/components/_theme-toggle.scss @@ -0,0 +1,46 @@ +.theme-toggle { + display: inline-flex; + align-items: center; + padding: 0.15rem; + border-radius: 0.35rem; + background: var(--th-bg-muted); + border: 0.05rem solid var(--th-border); + gap: 0.1rem; +} + +.theme-toggle-btn { + display: inline-flex; + align-items: center; + justify-content: center; + width: 2rem; + height: 2rem; + padding: 0; + border: none; + border-radius: 0.25rem; + background: transparent; + color: var(--th-text-muted); + cursor: pointer; + transition: + background-color 0.15s ease, + color 0.15s ease; + + &:hover { + color: var(--th-primary); + background: var(--th-bg-surface); + } + + &.is-active { + color: var(--th-primary); + background: var(--th-bg-surface); + box-shadow: 0 0 0 0.05rem var(--th-border); + } + + svg { + stroke: currentColor; + } +} + +.navbar-section-right { + margin-left: auto; + padding-right: 0.75rem; +} diff --git a/src/style/index.scss b/src/style/index.scss index a79cd65..7647369 100644 --- a/src/style/index.scss +++ b/src/style/index.scss @@ -1,6 +1,7 @@ // Variables and mixins @import "./variables"; @import "./mixins"; +@import "./theme"; @import "./spectre"; // Icons @@ -14,3 +15,4 @@ @import "./components/navbar"; @import "./components/panel"; @import "./components/media"; +@import "./components/theme-toggle"; diff --git a/src/tabs/generate/index.js b/src/tabs/generate/index.js index e7dfdd7..d985c79 100644 --- a/src/tabs/generate/index.js +++ b/src/tabs/generate/index.js @@ -22,11 +22,7 @@ import { Fragment, h } from "preact" import { T } from "../../components/Translations" import { ButtonImg } from "../../components/Controls" import { Save, ArrowLeft, Eye } from "preact-feather" -import { - useDatasContext, - useUiContext, - useDatasContextFn, -} from "../../contexts" +import { useDatasContext, useDatasContextFn } from "../../contexts" import header from "./header" import pioheader from "./pioheader" import footer from "./footer" @@ -36,6 +32,12 @@ import { useEffect, useState } from "preact/hooks" import { canShowField } from "../../configuration/visibility" import { auditPinConflicts } from "../../configuration/pinConflicts" import { mcuPinDefaults } from "../../configuration/mcuPinDefaults" +import { formatCameraPinDefines } from "../../configuration/cameraPins" +import { formatSerialPinDefines } from "../../configuration/serialPins" +import { + collectConfigurationSnapshot, + formatSnapshotComment, +} from "../../configuration/configurationSnapshot" const pioIcon = ( { + const snapshot = collectConfigurationSnapshot(data) return ( `// This file was generated by ESP3D-Configurator V${Version} \n` + header + convertToText(data) + - footer + formatCameraPinDefines(data, useDatasContextFn.getValueId) + + formatSerialPinDefines(useDatasContextFn.getValueId) + + footer + + formatSnapshotComment(snapshot) ) } @@ -89,7 +95,7 @@ const sections = { "framework = arduino\n" + "monitor_speed = 115200\n" + "monitor_echo = yes\n" + - "monitor_filters = send_on_enter, colorize, esp32_exception_decoder\n" + + "monitor_filters = send_on_enter, direct, esp32_exception_decoder\n" + "; set frequency to 240MHz\n" + ";board_build.f_cpu = 240000000L\n" + "; set frequency to 80MHz\n" + @@ -99,7 +105,7 @@ const sections = { "extra_scripts = pre:platformIO/extra_script.py\n" + "board_build.filesystem = $filesystem\n", build_flags: " -DCORE_DEBUG_LEVEL=0\n", - lib_ignore: "esp32-usb-serial\n", + lib_ignore: "\tTFT_eSPI\n\tesp32-usb-serial\n", ...mcuPinDefaults.esp32, }, esp32s2: { @@ -111,7 +117,7 @@ const sections = { "board_build.variant = esp32s2\n" + "monitor_speed = 115200\n" + "monitor_echo = yes\n" + - "monitor_filters = send_on_enter, colorize, esp32_exception_decoder\n" + + "monitor_filters = send_on_enter, direct, esp32_exception_decoder\n" + "; set frequency to 240MHz\n" + "board_build.f_cpu = 240000000L\n" + "; set frequency to 80MHz\n" + @@ -122,7 +128,7 @@ const sections = { "board_build.filesystem = $filesystem\n", build_flags: "\n -DCORE_DEBUG_LEVEL=0\n -DARDUINO_USB_CDC_ON_BOOT=0\n -DARDUINO_USB_MSC_ON_BOOT=0\n -DARDUINO_USB_DFU_ON_BOOT=0\n -DCONFIG_IDF_TARGET_ESP32S2=1\n", - // lib_ignore: "esp32-usb-serial\n", + lib_ignore: "\tTFT_eSPI\n", ...mcuPinDefaults.esp32s2, }, esp32s3: { @@ -135,7 +141,7 @@ const sections = { "board_build.arduino.memory_type = $memory_type\n" + "monitor_speed = 115200\n" + "monitor_echo = yes\n" + - "monitor_filters = send_on_enter, colorize, esp32_exception_decoder\n" + + "monitor_filters = send_on_enter, direct, esp32_exception_decoder\n" + "; set frequency to 240MHz\n" + "board_build.f_cpu = 240000000L\n" + "; set frequency to 80MHz\n" + @@ -159,7 +165,7 @@ const sections = { "board_build.variant = esp32c3\n" + "monitor_speed = 115200\n" + "monitor_echo = yes\n" + - "monitor_filters = send_on_enter, colorize, esp32_exception_decoder\n" + + "monitor_filters = send_on_enter, direct, esp32_exception_decoder\n" + "; set frequency to 240MHz\n" + "board_build.f_cpu = 240000000L\n" + "; set frequency to 80MHz\n" + @@ -182,7 +188,7 @@ const sections = { "board_build.variant = esp32c6\n" + "monitor_speed = 115200\n" + "monitor_echo = yes\n" + - "monitor_filters = send_on_enter, colorize, esp32_exception_decoder\n" + + "monitor_filters = send_on_enter, direct, esp32_exception_decoder\n" + "; set frequency to 240MHz\n" + "board_build.f_cpu = 240000000L\n" + "; set frequency to 80MHz\n" + @@ -203,7 +209,7 @@ const sections = { "framework = arduino\n" + "monitor_speed = 115200\n" + "monitor_echo = yes\n" + - "monitor_filters = send_on_enter, colorize, esp8266_exception_decoder\n" + + "monitor_filters = send_on_enter, direct, esp8266_exception_decoder\n" + "; set frequency to 160MHz\n" + "board_build.f_cpu = 160000000L\n" + "; set frequency to 40MHz\n" + @@ -225,7 +231,7 @@ const sections = { "framework = arduino\n" + "monitor_speed = 115200\n" + "monitor_echo = yes\n" + - "monitor_filters = send_on_enter, colorize, esp8266_exception_decoder\n" + + "monitor_filters = send_on_enter, direct, esp8266_exception_decoder\n" + "; set frequency to 160MHz\n" + "board_build.f_cpu = 160000000L\n" + "; set frequency to 40MHz\n" + @@ -250,11 +256,15 @@ const libIgnore = ({ hasBT, cameraName, displayName, - envName, }) => { - return sections[target].lib_ignore - ? "lib_ignore = " + sections[target].lib_ignore + "\n" - : "" + let ignore = sections[target].lib_ignore || "" + if (cameraName != "-1" && !ignore.includes("TFT_eSPI")) { + ignore += ignore ? "\tTFT_eSPI\n" : "TFT_eSPI\n" + } + if (displayName != "-1" && ignore.includes("TFT_eSPI")) { + ignore = ignore.replace(/\t?TFT_eSPI\n?/g, "") + } + return ignore ? "lib_ignore = " + ignore + (ignore.endsWith("\n") ? "" : "\n") : "" } const psramBuildFlags = (target, has_psram) => { @@ -313,6 +323,17 @@ const displayBuildFlags = (target, displayName) => { return "" } +const cameraBuildFlags = (target, cameraName) => { + if (cameraName == "-1") return "" + if (target == "esp32s3") { + return ( + " -DARDUINO_RUNNING_CORE=1\n" + + " -DARDUINO_EVENT_RUNNING_CORE=1\n" + ) + } + return "" +} + const buildFlags = ({ target, targetsize, @@ -320,14 +341,16 @@ const buildFlags = ({ hasEthernet, hasBT, has_psram, + cameraName, displayName, }) => { - //todo add Cam + const needsPsram = has_psram || cameraName != "-1" return ( "build_flags = " + sections[target].build_flags + displayBuildFlags(target, displayName) + - psramBuildFlags(target, has_psram) + psramBuildFlags(target, needsPsram) + + cameraBuildFlags(target, cameraName) ) } @@ -359,8 +382,8 @@ const partitionScheme = ( case "2": return "board_upload.flash_size = 4MB\nboard_build.partitions = minimal.csv" case "4": - if (((hasWifi || hasEthernet) && hasBT) || cameraName != "-1") { - return "board_upload.flash_size = 4MB\nboard_build.partitions = min_spiffs.csv\n" + if (cameraName != "-1") { + return "board_build.partitions = min_spiffs.csv\n" } return "board_upload.flash_size = 4MB\nboard_build.partitions = min_spiffs.csv\n" case "8": @@ -417,6 +440,7 @@ const convertToPioEnv = ({ hasEthernet, hasBT, has_psram, + cameraName, displayName, }) res = res.replace(/\$flash_mode/g, flash_mode) @@ -504,7 +528,7 @@ const convertToText = (data) => { return Object.keys(data).reduce((acc, item) => { return data[item].reduce((acc2, item2) => { if (item2.type == "group") { - if (!canshow(item2.depend)) return acc2 + if (!canshow(item2.depend) || item2.skipExport) return acc2 const content = item2.value.reduce((acc3, element) => { if (!canshow(element.depend)) return acc3 if (element.setting) { @@ -624,6 +648,7 @@ let showini = false const NavButtons = ({ previous, next, pinConflicts = [] }) => { const { configuration } = useDatasContext() const downloadBlocked = pinConflicts.length > 0 + return (
    {previous && ( diff --git a/src/tabs/step/index.js b/src/tabs/step/index.js index f6813c5..be828d3 100644 --- a/src/tabs/step/index.js +++ b/src/tabs/step/index.js @@ -34,7 +34,15 @@ import { canShowField } from "../../configuration/visibility" import { validateField } from "../../configuration/validateField" import { buildReservedLists } from "../../configuration/reservedResources" import { resolveEffectivePin } from "../../configuration/pinConflicts" -import { ArrowLeft, ArrowRight } from "preact-feather" +import { isPinOptionAllowed } from "../../configuration/pinCapabilities" +import { setFieldValueById } from "../../configuration/applyBoardPreset" +import { + CAMERA_PIN_KEYS, + isCustomCameraModel, + syncCameraPinsForModel, +} from "../../configuration/cameraPins" +import { ArrowLeft, ArrowRight, Info } from "preact-feather" +import { BoardPresetPicker } from "../../components/BoardPresetPicker" import pinsList from "./pins.json" import portsList from "./ports.json" @@ -176,12 +184,30 @@ const StepField = ({ ) const filteredOptions = optionsList ? optionsList.filter((opt) => { - return canshow( - opt.depend, - subelement.ispin || subelement.isport ? opt.value : null, - subelement.ispin || subelement.isport ? subelement.value : null, - subelement.ispin ? pinList : subelement.isport ? portList : null - ) + if ( + !canshow( + opt.depend, + subelement.ispin || subelement.isport ? opt.value : null, + subelement.ispin || subelement.isport + ? subelement.value + : null, + subelement.ispin ? pinList : subelement.isport ? portList : null + ) + ) { + return false + } + if ( + subelement.ispin && + (subelement.pinRole || + CAMERA_PIN_KEYS.includes(subelement.id)) + ) { + return isPinOptionAllowed( + subelement, + opt.value, + useDatasContextFn.getValueId + ) + } + return true }) : null if ( @@ -197,6 +223,13 @@ const StepField = ({ : subelement.description ) const [validation, setvalidation] = useState() + const cameraModel = useDatasContextFn.getValueId("cameratype") + const cameraPinReadOnly = + CAMERA_PIN_KEYS.includes(subelement.id) && + !isCustomCameraModel(cameraModel) + const showFootnote = + subelement.footnote && + (!subelement.footnoteWhenActive || subelement.value === true) useEffect(() => { setHelp( @@ -213,11 +246,18 @@ const StepField = ({ options={filteredOptions} value={value} type={type} + disabled={cameraPinReadOnly} {...rest} validationfn={generateValidation} setValue={(val, update = false) => { if (!update) { subelement.value = val + if (subelement.id === "cameratype" && configuration) { + if (val != "-1") { + setFieldValueById(configuration, "has_psram", true) + syncCameraPinsForModel(configuration, val) + } + } onFieldChange() setHelp( options @@ -233,13 +273,19 @@ const StepField = ({ {subelement.usedescforoptions && (
    {subelement.description}
    )} + {showFootnote && ( +
    +
    + )}
    ) } const StepTab = ({ previous, current, next }) => { - const { configuration } = useDatasContext() + const { configuration, configRevision } = useDatasContext() const [reserveVersion, setReserveVersion] = useState(0) const bumpFieldTree = () => { setReserveVersion((version) => version + 1) @@ -263,6 +309,9 @@ const StepTab = ({ previous, current, next }) => { {!isLoading && (
    + {current === "features" && ( + + )} {configuration.current[current] && configuration.current[current].map((element, index) => { if (element.type === "group") { @@ -270,13 +319,13 @@ const StepTab = ({ previous, current, next }) => { return ( {element.value.map((subelement, subindex) => ( { + if (mode === "dark") return "dark" + if (mode === "light") return "light" + if ( + typeof window !== "undefined" && + window.matchMedia("(prefers-color-scheme: dark)").matches + ) { + return "dark" + } + return "light" +} + +const getStoredThemeMode = () => { + try { + const stored = localStorage.getItem(STORAGE_KEY) + return THEME_MODES.includes(stored) ? stored : DEFAULT_MODE + } catch { + return DEFAULT_MODE + } +} + +const applyThemeMode = (mode) => { + const safeMode = THEME_MODES.includes(mode) ? mode : DEFAULT_MODE + const resolved = resolveTheme(safeMode) + const root = document.documentElement + root.dataset.themeMode = safeMode + root.dataset.theme = resolved + try { + localStorage.setItem(STORAGE_KEY, safeMode) + } catch { + /* ignore */ + } + return { mode: safeMode, resolved } +} + +const detachSystemListener = () => { + if (mediaQuery && mediaListener) { + mediaQuery.removeEventListener("change", mediaListener) + } + mediaQuery = null + mediaListener = null +} + +const attachSystemListener = () => { + detachSystemListener() + if (typeof window === "undefined") return + mediaQuery = window.matchMedia("(prefers-color-scheme: dark)") + mediaListener = () => { + if (getStoredThemeMode() === "auto") { + applyThemeMode("auto") + } + } + mediaQuery.addEventListener("change", mediaListener) +} + +const initTheme = () => { + const mode = getStoredThemeMode() + applyThemeMode(mode) + if (mode === "auto") { + attachSystemListener() + } else { + detachSystemListener() + } +} + +const setThemeMode = (mode) => { + const result = applyThemeMode(mode) + if (mode === "auto") { + attachSystemListener() + } else { + detachSystemListener() + } + return result +} + +export { + STORAGE_KEY, + THEME_MODES, + DEFAULT_MODE, + getStoredThemeMode, + resolveTheme, + applyThemeMode, + setThemeMode, + initTheme, +} diff --git a/src/translations/en.json b/src/translations/en.json index f7e1c74..b847a8a 100644 --- a/src/translations/en.json +++ b/src/translations/en.json @@ -17,5 +17,26 @@ "Firmware downloads are disabled until each GPIO is used by only one visible feature. Default (-1) pins are resolved for your MCU.": "Firmware downloads are disabled until each GPIO is used by only one visible feature. Default (-1) pins are resolved for your MCU.", "Download configuration.h": "Download configuration.h", "Download platformio.ini": "Download platformio.ini", - "Download WebUI": "Download WebUI" + "Download WebUI": "Download WebUI", + "Board preset": "Board preset", + "Preset": "Preset", + "Apply preset": "Apply preset", + "Apply board preset": "Apply board preset", + "This will overwrite settings that are defined in the preset. Other tabs are included.": "This will overwrite settings that are defined in the preset. Other tabs are included.", + "Load a starting profile for a common board, then fine-tune in each tab.": "Load a starting profile for a common board, then fine-tune in each tab.", + "Choose a board profile to load settings immediately (confirmation required). Fine-tune in each tab afterward.": "Choose a board profile to load settings immediately (confirmation required). Fine-tune in each tab afterward.", + "None (current settings)": "None (current settings)", + "Cancel": "Cancel", + "Apply": "Apply", + "Import configuration.h": "Import configuration.h", + "Imported": "Imported", + "settings from": "settings from", + "unknown ids ignored": "unknown ids ignored", + "Imported from configuration.h (embedded snapshot)": "Imported from configuration.h (embedded snapshot)", + "No configurator snapshot in this file. Download configuration.h from this tool first (JSON block at end of file).": "No configurator snapshot in this file. Download configuration.h from this tool first (JSON block at end of file).", + "Snapshot JSON is corrupted or invalid.": "Snapshot JSON is corrupted or invalid.", + "Snapshot format is invalid (missing values object).": "Snapshot format is invalid (missing values object).", + "Could not read configuration snapshot.": "Could not read configuration snapshot.", + "Could not read the selected file.": "Could not read the selected file.", + "You will choose a configuration.h file generated by this tool (snapshot at end of file).": "You will choose a configuration.h file generated by this tool (snapshot at end of file)." } diff --git a/tools/check_esp3d_reference.js b/tools/check_esp3d_reference.js new file mode 100644 index 0000000..1d7775d --- /dev/null +++ b/tools/check_esp3d_reference.js @@ -0,0 +1,67 @@ +/** + * Compares configurator camera options with ESP3D firmware in tmp/esp3d. + * Optional: run after dropping ESP3D sources into tmp/ + * + * Usage: node tools/check_esp3d_reference.js + */ + +const fs = require("fs") +const path = require("path") + +const definesPath = path.join( + __dirname, + "..", + "tmp", + "esp3d", + "src", + "include", + "esp3d_defines.h" +) +const devicesPath = path.join( + __dirname, + "..", + "src", + "configuration", + "tabs", + "devices.json" +) + +if (!fs.existsSync(definesPath)) { + console.log("ESP3D reference check skipped (tmp/esp3d not present)") + process.exit(0) +} + +const definesSource = fs.readFileSync(definesPath, "utf8") +const firmwareModels = [ + ...definesSource.matchAll(/#define (CAMERA_MODEL_\w+)\s+\d+/g), +].map((m) => m[1]) + +const devices = JSON.parse(fs.readFileSync(devicesPath, "utf8")) +const cameraGroup = devices.find((g) => g.id === "camera") +const cameratype = cameraGroup?.value?.find((f) => f.id === "cameratype") +const uiModels = (cameratype?.options || []) + .map((o) => o.value) + .filter((v) => v !== "-1") + +const missingInUi = firmwareModels.filter((m) => !uiModels.includes(m)) +const extraInUi = uiModels.filter((m) => !firmwareModels.includes(m)) + +let failed = false +if (missingInUi.length) { + failed = true + console.error("Camera models in firmware but missing in configurator UI:") + missingInUi.forEach((m) => console.error(` - ${m}`)) +} +if (extraInUi.length) { + failed = true + console.error("Camera models in configurator UI but unknown in firmware:") + extraInUi.forEach((m) => console.error(` - ${m}`)) +} + +if (failed) { + process.exit(1) +} + +console.log( + `ESP3D reference OK (${firmwareModels.length} firmware models, ${uiModels.length} in UI)` +) diff --git a/tools/migrate_camera_pins_to_select.js b/tools/migrate_camera_pins_to_select.js new file mode 100644 index 0000000..de5e18b --- /dev/null +++ b/tools/migrate_camera_pins_to_select.js @@ -0,0 +1,38 @@ +/** + * One-shot: camera GPIO fields → type select + ispin (string values). + * Usage: node tools/migrate_camera_pins_to_select.js + */ + +const fs = require("fs") +const path = require("path") + +const devicesPath = path.join( + __dirname, + "..", + "src", + "configuration", + "tabs", + "devices.json" +) + +const data = JSON.parse(fs.readFileSync(devicesPath, "utf8")) + +const migrateCameraPins = (groups) => { + let count = 0 + groups.forEach((group) => { + if (group.id !== "camerapins" || !Array.isArray(group.value)) return + group.value.forEach((field) => { + field.type = "select" + field.ispin = true + const v = field.value + field.value = + v === -1 || v === "-1" ? "-1" : String(v) + count++ + }) + }) + return count +} + +const n = migrateCameraPins(data) +fs.writeFileSync(devicesPath, JSON.stringify(data, null, 4) + "\n") +console.log(`Migrated ${n} camera pin fields to select+ispin`) diff --git a/tools/sync_pin_roles.js b/tools/sync_pin_roles.js new file mode 100644 index 0000000..4888a9f --- /dev/null +++ b/tools/sync_pin_roles.js @@ -0,0 +1,71 @@ +/** + * Copies pinRole / pinRoleWhen from pinUsageRoles.json into tab JSON fields. + * Usage: node tools/sync_pin_roles.js + */ + +const fs = require("fs") +const path = require("path") + +const tabsDir = path.join(__dirname, "..", "src", "configuration", "tabs") +const rolesPath = path.join( + __dirname, + "..", + "src", + "configuration", + "pinUsageRoles.json" +) + +const rolesDoc = JSON.parse(fs.readFileSync(rolesPath, "utf8")) +const roleById = rolesDoc.fields +const allowedRoles = Object.keys(rolesDoc.taxonomy.fieldRoles) + +const visitFields = (nodes, file, stats) => { + if (!Array.isArray(nodes)) return + nodes.forEach((node) => { + if (!node || typeof node !== "object") return + if (node.type === "group" && Array.isArray(node.value)) { + visitFields(node.value, file, stats) + return + } + if (typeof node.id !== "string") return + const spec = roleById[node.id] + if (!spec) return + + if (!allowedRoles.includes(spec.role)) { + throw new Error(`${file}: ${node.id} invalid role ${spec.role}`) + } + if (node.pinRole !== spec.role) { + node.pinRole = spec.role + stats.updated++ + } + if (spec.roleWhen?.sensortype) { + const when = { dependId: "sensortype", map: spec.roleWhen.sensortype } + if (JSON.stringify(node.pinRoleWhen) !== JSON.stringify(when)) { + node.pinRoleWhen = when + stats.whenUpdated++ + } + } else if (node.pinRoleWhen) { + delete node.pinRoleWhen + stats.whenCleared++ + } + stats.matched++ + }) +} + +let totalUpdated = 0 +let totalMatched = 0 + +for (const file of fs.readdirSync(tabsDir).filter((n) => n.endsWith(".json"))) { + const filePath = path.join(tabsDir, file) + const data = JSON.parse(fs.readFileSync(filePath, "utf8")) + const stats = { updated: 0, matched: 0, whenUpdated: 0, whenCleared: 0 } + visitFields(data, file, stats) + if (stats.updated || stats.whenUpdated || stats.whenCleared) { + fs.writeFileSync(filePath, JSON.stringify(data, null, 4) + "\n") + } + totalUpdated += stats.updated + totalMatched += stats.matched + console.log(`${file}: ${stats.matched} fields, ${stats.updated} pinRole updated`) +} + +console.log(`Done. ${totalMatched} fields, ${totalUpdated} pinRole writes.`) diff --git a/tools/validate_configuration.js b/tools/validate_configuration.js index 7f9b200..188adc9 100644 --- a/tools/validate_configuration.js +++ b/tools/validate_configuration.js @@ -31,6 +31,38 @@ const collectIds = (node, file, locations, ids) => { if (node.options) collectIds(node.options, file, `${locations}.options`, ids) } +const rolesPath = path.join( + __dirname, + "..", + "src", + "configuration", + "pinUsageRoles.json" +) +let pinRoleRegistry = {} +let allowedPinRoles = [] +try { + const rolesDoc = JSON.parse(fs.readFileSync(rolesPath, "utf8")) + pinRoleRegistry = rolesDoc.fields || {} + allowedPinRoles = Object.keys(rolesDoc.taxonomy?.fieldRoles || {}) +} catch (e) { + errors.push(`pinUsageRoles.json: ${e.message}`) +} + +const collectPinRoleFields = (node, file, locations, pinFields) => { + if (!node || typeof node !== "object") return + if (Array.isArray(node)) { + node.forEach((item, index) => + collectPinRoleFields(item, file, `${locations}[${index}]`, pinFields) + ) + return + } + if (typeof node.id === "string" && (node.ispin || pinRoleRegistry[node.id])) { + pinFields.push({ id: node.id, file, location: locations, field: node }) + } + if (node.value) collectPinRoleFields(node.value, file, `${locations}.value`, pinFields) + if (node.options) collectPinRoleFields(node.options, file, `${locations}.options`, pinFields) +} + const collectDependIds = (node, file, locations, depends) => { if (!node || typeof node !== "object") return if (Array.isArray(node)) { @@ -62,6 +94,7 @@ const jsonFiles = fs const ids = new Map() const depends = [] +const pinFields = [] for (const file of jsonFiles) { const filePath = path.join(tabsDir, file) @@ -78,6 +111,31 @@ for (const file of jsonFiles) { } collectIds(data, file, file, ids) collectDependIds(data, file, file, depends) + collectPinRoleFields(data, file, file, pinFields) +} + +for (const { id, file, location, field } of pinFields) { + if (!field.pinRole) { + errors.push(`${file} (${location}): "${id}" missing pinRole`) + continue + } + if (!allowedPinRoles.includes(field.pinRole)) { + errors.push( + `${file} (${location}): "${id}" invalid pinRole "${field.pinRole}"` + ) + } + const spec = pinRoleRegistry[id] + if (spec && field.pinRole !== spec.role) { + errors.push( + `${file} (${location}): "${id}" pinRole "${field.pinRole}" !== registry "${spec.role}"` + ) + } +} + +for (const registryId of Object.keys(pinRoleRegistry)) { + if (!pinFields.some((p) => p.id === registryId)) { + warnings.push(`pinUsageRoles.json: "${registryId}" has no tab field`) + } } for (const [id, locations] of ids) { @@ -156,6 +214,47 @@ for (const mcu of mcuKeys) { } } +const presetsPath = path.join( + __dirname, + "..", + "src", + "configuration", + "boardPresets.json" +) +let presetCount = 0 +try { + const presets = JSON.parse(fs.readFileSync(presetsPath, "utf8")) + if (!Array.isArray(presets)) { + errors.push("boardPresets.json: root must be an array") + } else { + const presetIds = new Set() + presets.forEach((preset, index) => { + presetCount += 1 + if (!preset.id) { + errors.push(`boardPresets[${index}]: missing id`) + return + } + if (presetIds.has(preset.id)) { + errors.push(`boardPresets: duplicate preset id "${preset.id}"`) + } + presetIds.add(preset.id) + if (!preset.values || typeof preset.values !== "object") { + errors.push(`boardPresets "${preset.id}": missing values object`) + return + } + Object.keys(preset.values).forEach((fieldId) => { + if (!idSet.has(fieldId)) { + errors.push( + `boardPresets "${preset.id}": unknown field id "${fieldId}"` + ) + } + }) + }) + } +} catch (e) { + errors.push(`boardPresets.json: ${e.message}`) +} + if (warnings.length) { console.warn("Warnings:") warnings.forEach((w) => console.warn(` - ${w}`)) @@ -168,5 +267,5 @@ if (errors.length) { } console.log( - `Configuration OK (${jsonFiles.length} tab files, ${idSet.size} unique field ids, ${depends.length} depend rules checked)` + `Configuration OK (${jsonFiles.length} tab files, ${presetCount} board presets, ${idSet.size} unique field ids, ${depends.length} depend rules checked)` ) From 6e4d7f1307a4172059227967674cf0d0d0af76b2 Mon Sep 17 00:00:00 2001 From: Luc <8822552+luc-github@users.noreply.github.com> Date: Wed, 3 Jun 2026 15:26:27 +0800 Subject: [PATCH 4/4] Update documentation --- API.md | 157 ++++++++++++++++++++++++++++++++++++++++-------- CONTRIBUTING.md | 97 ++++++++++++++++++++++++++++++ README.md | 72 +++++++++++++++++----- 3 files changed, 286 insertions(+), 40 deletions(-) create mode 100644 CONTRIBUTING.md diff --git a/API.md b/API.md index 7073e2a..be52bc1 100644 --- a/API.md +++ b/API.md @@ -4,29 +4,54 @@ This document describes how the configurator defines UI tabs, form fields, depen Configuration is split per tab under `src/configuration/` (one JSON file per wizard section). +## Documentation map + +| Document | Audience | Contents | +|----------|----------|----------| +| [README.md](README.md) | Everyone | Project overview, live demo link, dev quick start | +| [CONTRIBUTING.md](CONTRIBUTING.md) | Developers | Clone, scripts, PR checklist, CI | +| **API.md** (this file) | Developers | Schema, modules, pins, export/import, presets | + ## Architecture ``` src/ configuration/ - index.js # Aggregates all tab sections into one object - visibility.js # Shared depend / visibility rules (UI + generate) - mcuPinDefaults.js # MCU default GPIO for SPI when pin = Default (-1) - pinConflicts.js # Resolve effective GPIO + conflict audit - validateField.js # Per-field validation in step tabs - reservedResources.js # Cross-tab pin/port reservation lists - tabs/ - features.json # Tab data (array of groups) - features.js # import ... from "./features.json" - network.json - ... - pages/config/ - steps.js # Tab bar order, routes, icons (UI registry) + index.js # Aggregates all tab sections + visibility.js # depend / visibility (UI + generate) + applyBoardPreset.js # Apply preset or import snapshot by field id + boardPresets.json # Built-in board starting profiles + configurationSnapshot.js # JSON snapshot in configuration.h (export/import) + mcuPinDefaults.js # MCU default GPIO for SPI when pin = Default (-1) + pinCapabilities.js # Filter/validate GPIO by MCU + pinRole + pinUsageRoles.json # Field id → pinRole registry + psramReservedPins.js # PSRAM / camera reserved GPIO per MCU + serialPinDefaults.js # UART default GPIO labels for -1 + serialPins.js # Extra #defines for serial RX/TX in configuration.h + cameraPins.js # Camera model pin maps + #defines + cameraPinMaps.json + pinConflicts.js # Resolve effective GPIO + conflict audit + validateField.js # Per-field validation in step tabs + reservedResources.js # Cross-tab pin/port reservation lists + tabs/ # One JSON (+ .js re-export) per wizard section + components/ + BoardPresetPicker/ # Preset dropdown + Import configuration.h (Features) + ThemeToggle/ # Light / Auto / Dark (navbar) + theme/theme.js # Theme persistence (localStorage) contexts/ - DatasContext.js # Loads aggregated configuration + DatasContext.js # configuration ref, configRevision, importSnapshot + UiContext.js # Modals, toasts + pages/config/ + steps.js # Tab bar order, routes, icons tabs/ - step/ # Renders step tabs; pins.json / ports.json - generate/ # Builds configuration.h and platformio.ini + step/ # Step tabs; pins.json / ports.json + generate/ # configuration.h + platformio.ini + translations/ + en.json # Base strings for T() +tools/ + validate_configuration.js # npm run validate:config + sync_pin_roles.js # npm run sync:pin-roles + check_esp3d_reference.js # npm run check:esp3d (needs tmp/esp3d) ``` | Layer | Role | @@ -34,10 +59,72 @@ src/ | `configTabs` (`steps.js`) | Wizard navigation: label, route, link id, icon, `section` key | | `tabs/
    .json` | Field definitions for one configurator step | | `configuration/index.js` | `{ features: [...], network: [...], ... }` for the app | -| `GenerateTab` | Reads full configuration and exports `configuration.h` / `platformio.ini` | +| `BoardPresetPicker` | Features tab: presets + import; same confirm dialog as preset apply | +| `GenerateTab` | Reads configuration and exports `configuration.h` / `platformio.ini` | +| `DatasContext` | Mutable `configuration.current`; `configRevision` bumps UI after import | The `default` section exists in `configuration/index.js` (from `defaults.json`) and is included in generated headers, but it has **no dedicated tab** in the UI. +## Development workflow + +### Prerequisites + +- **Node.js** 20+ (matches [CI](.github/workflows/ci.yml)) +- **npm** (lockfile: commit `package-lock.json` if present) + +### Install and run + +```bash +npm ci # or npm install +npm run dev # webpack dev server + express (see config/server.js) +npm run build # production bundle → build/ +``` + +Open the URL printed by the dev server (or serve `build/` statically for a production-like test). + +### npm scripts + +| Script | Command | Purpose | +|--------|---------|---------| +| `dev` | `concurrently` front + server | Local development | +| `front` | `webpack serve` | UI only | +| `server` | `nodemon config/server.js` | Dev backend | +| `build` | `webpack --config config/webpack.prod.js` | Release build | +| `validate:config` | `node tools/validate_configuration.js` | Unique ids, `depend` refs, pin roles | +| `sync:pin-roles` | `node tools/sync_pin_roles.js` | Sync `pinRole` from `pinUsageRoles.json` into tab JSON | +| `check:esp3d` | `node tools/check_esp3d_reference.js` | Camera models vs `tmp/esp3d` (optional) | + +**Before opening a PR:** `npm run validate:config` and `npm run build` (same as CI). + +### Continuous integration + +GitHub Actions workflow `.github/workflows/ci.yml` on push/PR to `main` / `master`: + +1. `npm ci` +2. `npm run validate:config` +3. `npm run build` + +### Internationalization (i18n) + +UI strings use `T("English key")` from `src/components/Translations/index.js`. + +- Base catalog: `src/translations/en.json` +- New user-visible text: add the **English sentence as key and value** in `en.json`, then use `T("…")` in components +- Toasts use the `content` property (not `message`); import feedback can pass `literal: true` to skip re-translation + +### UI theme + +- Toggle in navbar: **Light** / **Auto** / **Dark** (`src/components/ThemeToggle`, `src/theme/theme.js`) +- Stored in `localStorage` key `esp3d-configurator-ui-theme` (default `auto` → follows `prefers-color-scheme`) +- Resolved theme on ``; tokens in `src/style/_theme.scss` + +### Manual test checklist (config changes) + +1. Features → change MCU, preset, **Import configuration.h** (confirm dialog → file → green message + toast). +2. Walk affected tabs (pins, serial, camera). +3. Download → preview `configuration.h` (snapshot at end) and `platformio.ini`; fix pin conflicts if download is blocked. +4. Re-import the downloaded `configuration.h` and confirm values match. + ## Configurator tabs (UI) Defined in `src/pages/config/steps.js` (`configTabs`): @@ -170,6 +257,8 @@ There is no separate settings file. **Download configuration.h** already embeds Older `configuration.h` files without this block cannot be imported through this flow. +**Groups omitted from the header (still in snapshot):** e.g. **Board description** (`features` group `mcu`) uses `skipExport: true` on the group — UI unchanged, values only in JSON snapshot, not as comment banner in `configuration.h`. + ## Maintenance notes - Avoid duplicate `id` values (e.g. multiple `ssdpmodelename` for different MCUs). Duplicates break `getValueId()` and can cause React key warnings; use distinct ids or a single control with `depend` on options. @@ -228,9 +317,11 @@ npm run check:esp3d This checks that every `CAMERA_MODEL_*` in `tmp/esp3d/src/include/esp3d_defines.h` appears in the configurator UI. -## Board presets +## Board presets and import -Starting profiles for common boards live in `src/configuration/boardPresets.json`. Each preset has: +UI: `src/components/BoardPresetPicker` on the **Features** tab (preset `