diff --git a/lib/TFT_eSPI_compat/README.md b/lib/TFT_eSPI_compat/README.md new file mode 100644 index 0000000000..6640ffa660 --- /dev/null +++ b/lib/TFT_eSPI_compat/README.md @@ -0,0 +1,89 @@ +# TFT_eSPI_compat + +A compatibility wrapper for the [TFT_eSPI](https://github.com/Bodmer/TFT_eSPI) library in WLED usermods. Handles two things that every TFT_eSPI usermod would otherwise need to sort out independently: + +1. **SPIFFS stub** — TFT_eSPI pulls in `SPIFFS.h` when `SMOOTH_FONT` is enabled, but SPIFFS is not available on modern ESP32 Arduino platforms. A minimal stub header is injected automatically, but placed last on the include search path. This ensures that, if present, the real header is found first and used instead. + +2. **Display setup** — TFT_eSPI requires a *User Setup* header to be processed before any of its headers. This wrapper generates a `tft_setup.h` at build time (using TFT_eSPI's own `__has_include` hook) and injects it into the compiler's search path, so usermods don't need their own build scripts for this. + +--- + +## Using TFT_eSPI_compat in a usermod + +Depend on `TFT_eSPI_compat` instead of `TFT_eSPI` directly. In your usermod's `library.json`: + +```json +{ + "name": "my_display_usermod", + "build": { "libArchive": false }, + "dependencies": { + "TFT_eSPI_compat": "*" + } +} +``` + +TFT_eSPI itself is pulled in transitively. In your source, include it normally: + +```cpp +#include +``` + +--- + +## Configuring the display setup + +TFT_eSPI needs to know which display driver and pin mapping to use. Set `custom_display_setup` in your environment's `platformio_override.ini`: + +```ini +custom_display_setup = User_Setups/Setup23_ST7789.h +``` + +The value can be: +- A path relative to any include directory (e.g. a filename inside TFT_eSPI's `User_Setups/` folder), or +- An absolute path to a custom setup header anywhere on disk. + +If `custom_display_setup` is not set, no display setup is applied — your usermod must either set a default (see below) or configure the display another way (e.g. via global `build_flags`). + +--- + +## Providing a default setup (for usermod authors) + +If your usermod targets a specific display, you can declare a preferred default that users can still override via `custom_display_setup`. Add a short `extraScript` to your `library.json`: + +```json +"build": { + "extraScript": "set_build_flags.py", + "libArchive": false +} +``` + +```python +# set_build_flags.py +Import("env") + +global_env = DefaultEnvironment() +global_env.SetDefault(TFT_DISPLAY_SETUP_DEFAULT="User_Setups/Setup25_TTGO_T_Display.h") + +# Add any other usermod-specific build flags here (pin defaults, etc.) +``` + +`SetDefault` only takes effect when neither `custom_display_setup` nor `TFT_DISPLAY_SETUP_DEFAULT` has already been set, so it never overrides a user's explicit configuration. + +`TFT_DISPLAY_SETUP_DEFAULT` is read at build time (when `tft_setup.h` is generated), after all library extraScripts have run. + +--- + +## How it works + +`configure.py` appends two directories to the compiler search path for all libraries (including TFT_eSPI) — appended rather than prepended, so a real `SPIFFS.h` found anywhere else on the path always takes priority over the stub: + +- `shims/` — contains `SPIFFS.h` (stub) and `User_Setup.h` (dependency-tracking shim) +- `$BUILD_DIR/TFT_eSPI_compat/` — contains the generated `tft_setup.h` + +TFT_eSPI.h checks `__has_include()` at compile time; because `tft_setup.h` is built by a SCons `Command` node before any TFT_eSPI source is compiled, it is always present. `User_Setup.h` carries a plain `#include ` that gives SCons's header scanner the dependency edge it needs to guarantee ordering. + +--- + +## TFT_eSPI version + +The pinned TFT_eSPI version is declared in `library.json`. All usermods depending on `TFT_eSPI_compat` share this version; update it in one place when needed. diff --git a/lib/TFT_eSPI_compat/configure.py b/lib/TFT_eSPI_compat/configure.py new file mode 100644 index 0000000000..4c02720fbe --- /dev/null +++ b/lib/TFT_eSPI_compat/configure.py @@ -0,0 +1,136 @@ +Import("env") +import json +import os +from os.path import realpath + +import SCons.Errors +from SCons.Script import Action + +global_env = DefaultEnvironment() + +shims_dir = realpath("shims") +# BUILD_DIR is itself defined as "$PROJECT_BUILD_DIR/$PIOENV" (see PlatformIO's +# builder/main.py) — env["BUILD_DIR"] returns that raw, unexpanded template +# string, not a real path. Without subst(), os.path.join() below would create +# a literal "./shims/$PROJECT_BUILD_DIR/$PIOENV/TFT_eSPI_compat" directory. +build_shims_dir = os.path.join(global_env.subst("$BUILD_DIR"), "TFT_eSPI_compat") + +# Create the generated-file directory now so get_include_dirs() reports it +# and so SCons's FindFile can locate Command nodes inside it. +os.makedirs(build_shims_dir, exist_ok=True) + +# Inject shims/ and build_shims_dir into DefaultEnvironment(), so they'll be +# visible anywhere in the build. They're appended, not prepended, so any +# real SPIFFS.h or tft_setup.h will take precedence over these. +global_env.AppendUnique(CPPPATH=[shims_dir, build_shims_dir]) + +# Also add to our own lib env so load_usermods.py propagates them to usermods +# via cached_add_includes → dep.get_include_dirs(). +env.AppendUnique(CPPPATH=[shims_dir, build_shims_dir]) + +# --------------------------------------------------------------------------- +# Generate tft_setup.h — proper SCons target +# --------------------------------------------------------------------------- +# TFT_eSPI.h checks __has_include(); providing the file lets us +# inject the display config via TFT_eSPI's own hook without patching it. +# +# The file is generated from the resolved display-setup configuration so +# SCons knows to rebuild it (and therefore recompile TFT_eSPI) exactly when +# that configuration changes — not on every build. +# +# SCons's CScanner does not parse __has_include(), so shims/User_Setup.h +# carries a plain #include that gives the scanner a normal +# include directive to follow, ensuring the generated file is built before +# any TFT_eSPI source is compiled. + +_lib_env = env # capture before the function definition shadows the name + +# Enumerating every file that could influence custom_display_setup / +# TFT_DISPLAY_SETUP_DEFAULT is unbounded, so we depend on the *resolved +# value* instead, via a real intermediate file (SCons Value() nodes don't +# track post-construction writes for signature purposes, so they can't be +# used as a build-time-computed dependency). Resolution needs +# GetLibBuilders(), which isn't populated until build time, so it happens +# in a build action rather than here at parse time. AlwaysBuild reruns the +# resolution every build, but we only rewrite the file when the resolved +# value actually changes. +def _resolve_display_setup(target, source, env): # argument names are required by SCons + genv = DefaultEnvironment() + all_lbs = _lib_env.GetLibBuilders() + + display_setup = ( + genv.GetProjectOption("custom_display_setup", "") + or genv.get("TFT_DISPLAY_SETUP_DEFAULT", "") + ) + + setup_path = None + if display_setup: + if os.path.isfile(display_setup): + setup_path = display_setup + else: + search_paths = [ + genv["PROJECT_SRC_DIR"], + genv["PROJECT_INCLUDE_DIR"], + *[d for lb in all_lbs for d in lb.get_include_dirs()], + ] + setup_path = next( + ( + os.path.join(str(p), display_setup) + for p in search_paths + if os.path.isfile(os.path.join(str(p), display_setup)) + ), + None, + ) + + target_path = str(target[0]) + new_contents = json.dumps([display_setup, setup_path]) + if os.path.isfile(target_path): + with open(target_path) as f: + if f.read() == new_contents: + return + with open(target_path, "w") as f: + f.write(new_contents) + + +_resolved_display_setup = global_env.Command( + os.path.join(build_shims_dir, "display_setup.resolved.json"), + [], + Action(_resolve_display_setup, cmdstr=None), +) +global_env.AlwaysBuild(_resolved_display_setup) +# NoCache: this target has no real sources, so its CacheDir signature never +# changes — without this, CacheDir would keep serving the first build's +# file instead of re-running _resolve_display_setup. +global_env.NoCache(_resolved_display_setup) +# Precious: SCons deletes a target before rebuilding it by default, which +# would defeat the write-if-changed check above (it needs to read the +# previous build's file to compare against). +global_env.Precious(_resolved_display_setup) + + +def _generate_tft_setup(target, source, env): # argument names are required by SCons + with open(str(source[0])) as f: + display_setup, setup_path = json.load(f) + + lines = ["// Generated by TFT_eSPI_compat — do not edit\n", "#pragma once\n"] + + if display_setup: + if setup_path: + escaped = setup_path.replace("\\", "\\\\") + lines.append(f'#include "{escaped}"\n') + else: + raise SCons.Errors.StopError( + f"TFT_eSPI_compat: display setup '{display_setup}' not found " + "in any include path" + ) + + os.makedirs(os.path.dirname(str(target[0])), exist_ok=True) + with open(str(target[0]), "w") as f: + f.writelines(lines) + + +global_env.Command( + os.path.join(build_shims_dir, "tft_setup.h"), + _resolved_display_setup, + Action(_generate_tft_setup, cmdstr="Generating tft_setup.h"), +) diff --git a/lib/TFT_eSPI_compat/library.json b/lib/TFT_eSPI_compat/library.json new file mode 100644 index 0000000000..c096b2edfb --- /dev/null +++ b/lib/TFT_eSPI_compat/library.json @@ -0,0 +1,11 @@ +{ + "name": "TFT_eSPI_compat", + "build": { + "extraScript": "configure.py", + "libArchive": false + }, + "platforms": ["espressif32"], + "dependencies": { + "TFT_eSPI": "https://github.com/Bodmer/TFT_eSPI.git#16e37595040eac69cd628e4bffb56fc30cad6299" + } +} diff --git a/lib/TFT_eSPI_compat/shims/SPIFFS.h b/lib/TFT_eSPI_compat/shims/SPIFFS.h new file mode 100644 index 0000000000..5787479a71 --- /dev/null +++ b/lib/TFT_eSPI_compat/shims/SPIFFS.h @@ -0,0 +1,18 @@ +#pragma once +// Stub: TFT_eSPI includes SPIFFS.h when SMOOTH_FONT is defined, but the +// usermods that use it don't load fonts from a filesystem. This shim +// satisfies the include and the `fs::FS &fontFS = SPIFFS` declaration in +// Smooth_font.h without pulling in the SPIFFS Arduino library. +// SPIFFS-backed font loading will silently do nothing at runtime. +// +// shims/ is appended (not prepended) to CPPPATH in configure.py, so this is +// only reached by the compiler's #include search when no real SPIFFS.h +// exists earlier on the include path. +#include + +class SPIFFSStub : public fs::FS { +public: + SPIFFSStub() : fs::FS(nullptr) {} +}; + +inline SPIFFSStub SPIFFS; diff --git a/lib/TFT_eSPI_compat/shims/User_Setup.h b/lib/TFT_eSPI_compat/shims/User_Setup.h new file mode 100644 index 0000000000..a1696d3ff5 --- /dev/null +++ b/lib/TFT_eSPI_compat/shims/User_Setup.h @@ -0,0 +1,12 @@ +#pragma once +// TFT_eSPI_compat: dependency shim. +// +// At compile time this file is NOT used — TFT_eSPI.h's __has_include() +// fires first, so USER_SETUP_LOADED is defined before User_Setup_Select.h ever +// reaches the fallback #include . +// +// The only purpose of this file is to give SCons's C/C++ header scanner a plain +// #include directive that it can follow to discover tft_setup.h as a build +// dependency, ensuring the Command that generates tft_setup.h runs before any +// TFT_eSPI source file is compiled. +#include diff --git a/usermods/TTGO-T-Display/README.md b/usermods/TTGO-T-Display/README.md index 439f9832dd..3f4d2a6494 100644 --- a/usermods/TTGO-T-Display/README.md +++ b/usermods/TTGO-T-Display/README.md @@ -1,91 +1,114 @@ # TTGO T-Display ESP32 with 240x135 TFT via SPI with TFT_eSPI -This usermod enables use of the TTGO 240x135 T-Display ESP32 module -for controlling WLED and showing the following information: + +This usermod is a TFT display driver for WLED, using the TFT_eSPI library, originally designed for the TTGO 240x135 T-Display ESP32 module. It can drive any TFT display with sufficient resolution that is supported by TFT_eSPI. The supplied `platformio_override.ini` provides a ready-made configuration for the TTGO T-Display board specifically — see [Using with other boards](#using-with-other-boards) if you have different hardware. + +When running, the display shows: + * Current SSID -* IP address, if obtained - * If connected to a network, current brightness percentage is shown - * In AP mode, AP, IP and password are shown +* IP address + * In station mode: IP address and current brightness percentage + * In AP mode: AP SSID, IP address, and password * Current effect * Current palette -* Estimated current in mA (NOTE: for this to be a reasonable value, the correct LED type must be specified in the LED Prefs section) +* Estimated current in mA (requires correct LED type in LED preferences) -Button pin is mapped to the onboard button adjacent to the reset button of the TTGO T-Display board. +The display turns off automatically after 5 minutes with no state change, and wakes on the next detected change. The usermod can also be fully disabled via the WLED config UI, which turns off the backlight and skips all display work. Display on/off status is shown in the WLED Info panel under **TFT Display**. + +When using the T-Display environment, button pin 35 is mapped to the onboard button adjacent to the reset button (right side of the USB-C connector when oriented downward — see hardware photo). The button can be configured via the Macros section of the WLED web UI, e.g. to cycle presets. I have designed a 3D printed case around this board and an ["ElectroCookie"](https://amzn.to/2WCNeeA) project board, a [level shifter](https://amzn.to/3hbKu18), a [buck regulator](https://amzn.to/3mLMy0W), and a DC [power jack](https://amzn.to/3phj9NZ). I use 12V WS2815 LED strips for my projects, and power them with 12V power supplies. The regulator supplies 5V for the ESP module and the level shifter. If there is any interest in this case which elevates the board and display on custom extended standoffs to place the screen at the top of the enclosure (with accessible buttons), let me know, and I will post the STL files. It is a bit tricky to get the height correct, so I also designed a one-time use 3D printed solder fixture to set the board in the right location and at the correct height for the housing. (It is one-time use because it has to be cut off after soldering to be able to remove it). I didn't think the effort to make it in multiple pieces was worthwhile. Based on a rework of the ssd1306_i2c_oled_u8g2 usermod from the WLED repo. ## Hardware + ![Hardware](assets/ttgo_hardware1.png) -![Hardware](assets/ttgo-tdisplay-enclosure1a.png) -![Hardware](assets/ttgo-tdisplay-enclosure2a.png) -![Hardware](assets/ttgo-tdisplay-enclosure3a.png) -![Hardware](assets/ttgo-tdisplay-enclosure3a.png) +![Enclosure](assets/ttgo-tdisplay-enclosure1a.png) +![Enclosure](assets/ttgo-tdisplay-enclosure2a.png) +![Enclosure](assets/ttgo-tdisplay-enclosure3a.png) +![Enclosure](assets/ttgo-tdisplay-enclosure4a.png) -## Github reference for TTGO-Tdisplay +## Github reference for TTGO-T-Display * [TTGO T-Display](https://github.com/Xinyuan-LilyGO/TTGO-T-Display) ## Requirements + Functionality checked with: -* TTGO T-Display + +* TTGO T-Display (ESP32, 240×135 ST7789 display) * PlatformIO -* Group of 4 individual Neopixels from Adafruit and several full strings of 12v WS2815 LEDs. +* A group of 4 individual Neopixels from Adafruit and several full strings of 12V WS2815 LEDs. * The hardware design shown above should be limited to shorter strings. For larger strings, I use a different setup with a dedicated 12v power supply and power them directly from said supply (in addition to dropping the 12v to 5v with a buck regulator for the ESP module and level shifter). -## Setup Needed: -* As with all usermods, copy the usermod.cpp file from the TTGO-T-Display usermod folder to the wled00 folder (replacing the default usermod.cpp file). +## Setup (TTGO T-Display board) -## Platformio Requirements -### Platformio.ini changes -Under the root folder of the project, in the `platformio.ini` file, uncomment the `TFT_eSPI` line within the [common] section, under `lib_deps`: -```ini -# platformio.ini -... -[common] -... -lib_deps = - ... - #For use of the TTGO T-Display ESP32 Module with integrated TFT display uncomment the following line - #TFT_eSPI -... -``` +The following steps are specific to the TTGO T-Display board. For other hardware, see [Using with other boards](#using-with-other-boards). -In the `platformio.ini` file, you must change the environment setup to build for just the esp32dev platform as follows: +### 1. Copy `platformio_override.ini` -Comment out the line described below: -```ini -# Release binaries -; default_envs = nodemcuv2, esp8266_2m, esp01_1m_full, esp32dev, esp32_eth, esp32s2_saola, esp32c3 -``` -and uncomment the following line in the 'Single binaries' section: -```ini -default_envs = esp32dev -``` -Save the `platformio.ini` file. Once saved, the required library files should be automatically downloaded for modifications in a later step. +Copy `usermods/TTGO-T-Display/platformio_override.ini` to the root of your WLED project folder. -### Platformio_overrides.ini (added) -Copy the `platformio_overrides.ini` file which is contained in the `usermods/TTGO-T-Display/` folder into the root of your project folder. This file contains an override that remaps the button pin of WLED to use the on-board button to the right of the USB-C connector (when viewed with the port oriented downward - see hardware photo). +This file: +- Sets the active build environment to `WLED_T-Display` +- Sets `DATA_PINS=2` and `BTNPIN=35` for the T-Display's onboard button and LED data pin +- Includes this usermod via `custom_usermods` -### TFT_eSPI Library Adjustments (board selection) -You need to modify a file in the `TFT_eSPI` library to select the correct board. If you followed the directions to modify and save the `platformio.ini` file above, the `User_Setup_Select.h` file can be found in the `/.pio/libdeps/esp32dev/TFT_eSPI_ID1559` folder. +Once the platformio_override.ini file has been copied as described above, the platformio.ini file isn't actually changed, but it is helpful to save the platformio.ini file in the VS Code application. This should trigger the download of the library dependencies. -Modify the `User_Setup_Select.h` file as follows: -* Comment out the following line (which is the 'default' setup file): -```ini -//#include // Default setup is root library folder -``` -* Uncomment the following line (which points to the setup file for the TTGO T-Display): -```ini -#include // Setup file for ESP32 and TTGO T-Display ST7789V SPI bus TFT -``` +### 2. Build and flash + +Select the `WLED_T-Display` environment in PlatformIO (it should appear in the bottom toolbar) and build. The display will show **Loading…** on first boot, then the connection info. + +## Using with other boards + +The display driver itself requires only a TFT_eSPI-compatible display with at least 240×135 pixels. To adapt it to different hardware: + +### Quick setup + +1. **Create a `platformio_override.ini` file** with a new environment for your board. Use `extends` to reference one of the pre-existing board environments from `platformio.ini` to avoid copy-and-pasting the complete configuration. + +2. **Add this usermod** to your environment by appending `TTGO-T-Display` to your `custom_usermods` line. + +3. **Set `custom_display_setup` to point to your display setup**. See the detailed discussion below. + +### Selecting a display setup + +TFT_eSPI requires a hardware configuration header. The default for this usermod is `User_Setups/Setup25_TTGO_T_Display.h` from the TFT_eSPI library, which has the definitions for the standard TTGO T-Display board. + +To use a different display, add `custom_display_setup` to your `[env:]` section in `platformio_override.ini`: -Build the file. If you see a failure like this: ```ini -xtensa-esp32-elf-g++: error: wled00\wled00.ino.cpp: No such file or directory -xtensa-esp32-elf-g++: fatal error: no input files +# Use a different built-in TFT_eSPI setup: +custom_display_setup = User_Setups/Setup23_ST7789.h + +# Or point to a custom header anywhere on disk or in wled00/: +custom_display_setup = /absolute/path/to/MyDisplay.h +custom_display_setup = MyDisplay.h ``` -try building again. Sometimes this happens on the first build attempt and subsequent attempts build correctly. -## Arduino IDE -- UNTESTED +The value is resolved in this order: +1. As an absolute path (used directly if the file exists) +2. Relative to any include directory: `wled00/`, project include dir, and all TFT_eSPI include paths + +A custom header must define all required TFT_eSPI `#define`s for your hardware (driver, pins, SPI frequency, etc.). See the `User_Setups/` folder of TFT_eSPI for examples. + +You do not need to set `USER_SETUP_LOADED`. + +> **Note:** Display setup injection is handled by the `TFT_eSPI_compat` shim library in `lib/`. This library also provides workarounds for some library bugs, such as a hard dependency on the SPIFFS stub so SMOOTH_FONT can be compiled on platforms without SPIFFS support. + +> **Note:** Many TFT_eSPI usage examples suggest defining the setup as `-D XX_PIN=N` settings in `build_flags`. That approach will not work correctly with the `TFT_eSPI_compat` library; use of a display setup `.h` file is required. + +## Pin conflict detection + +During `setup()`, the usermod registers all TFT pins (MOSI, SCLK, CS, DC, RST, BL, and optionally MISO/TOUCH_CS) with WLED's PinManager. If any pin is already claimed by another usermod or WLED core function, the display will be disabled at startup and a debug message printed. + +## Runtime configuration + +The usermod exposes one config key visible in the WLED settings UI: + +| Key | Default | Description | +|-----|---------|-------------| +| `enabled` | `true` | Enable or disable the display. When disabled, the backlight is turned off immediately. | + +Settings are persisted in `cfg.json` under the `"TFT Display"` key. diff --git a/usermods/TTGO-T-Display/library.json b/usermods/TTGO-T-Display/library.json new file mode 100644 index 0000000000..4eb025968e --- /dev/null +++ b/usermods/TTGO-T-Display/library.json @@ -0,0 +1,10 @@ +{ + "name": "TTGO-T-Display", + "dependencies": { + "TFT_eSPI_compat": "*" + }, + "build": { + "extraScript": "set_build_flags.py", + "libArchive": false + } +} diff --git a/usermods/TTGO-T-Display/platformio_override.ini b/usermods/TTGO-T-Display/platformio_override.ini new file mode 100644 index 0000000000..cd44a60a92 --- /dev/null +++ b/usermods/TTGO-T-Display/platformio_override.ini @@ -0,0 +1,21 @@ +# Example PlatformIO Project Configuration Override +# ------------------------------------------------------------------------------ +# Copy to platformio_override.ini to activate overrides +# ------------------------------------------------------------------------------ +# Please visit documentation: https://docs.platformio.org/page/projectconf.html + +[platformio] +#default_envs = WLED_tasmota_1M # define as many as you need +default_envs = WLED_T-Display # define as many as you need + +#---------- +# SAMPLE +#---------- +[env:WLED_T-Display] +extends = env:esp32dev +upload_speed = 921600 +monitor_speed = 115200 +build_flags = ${common.build_flags} ${esp32.build_flags} -D WLED_RELEASE_NAME=\"WLED_T-Display\" + -D DATA_PINS=2 ; Data pin on TTGO T-Display board + -D BTNPIN=35 ; Button on TTGO T-Display board +custom_usermods = ${env:esp32dev.custom_usermods} TTGO-T-Display diff --git a/usermods/TTGO-T-Display/set_build_flags.py b/usermods/TTGO-T-Display/set_build_flags.py new file mode 100644 index 0000000000..f5d202ce30 --- /dev/null +++ b/usermods/TTGO-T-Display/set_build_flags.py @@ -0,0 +1,7 @@ +Import("env") + +global_env = DefaultEnvironment() + +# Provide TTGO's preferred display setup as the default for TFT_eSPI_compat. +# Users can override this by setting custom_display_setup in platformio_override.ini. +global_env.SetDefault(TFT_DISPLAY_SETUP_DEFAULT="User_Setups/Setup25_TTGO_T_Display.h") diff --git a/usermods/TTGO-T-Display/usermod.cpp b/usermods/TTGO-T-Display/usermod.cpp index d8dcb29996..c57c0f5112 100644 --- a/usermods/TTGO-T-Display/usermod.cpp +++ b/usermods/TTGO-T-Display/usermod.cpp @@ -17,92 +17,132 @@ * when a change in the dipslayed info is detected (within a 5 second interval). */ - -//Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) - #include "wled.h" -#include #include +#include #include "WiFi.h" #include -#ifndef TFT_DISPOFF -#define TFT_DISPOFF 0x28 -#endif +#define DISPLAY_HAS_BACKLIGHT defined(TFT_BL) && defined(TFT_BACKLIGHT_ON) && (TFT_BL >= 0) -#ifndef TFT_SLPIN -#define TFT_SLPIN 0x10 -#endif +// How often we are redrawing screen +#define USER_LOOP_REFRESH_RATE_MS 5000 + +class TTGO_T_DisplayMod : public Usermod { + + // Member variables + bool enabled = true; + bool ready = false; + TFT_eSPI tft; // Invoke custom library + // needRedraw marks if redraw is required to prevent often redrawing. + bool needRedraw = true; + // Next variables hold the previous known values to determine if redraw is + // required. + String knownSsid = ""; + IPAddress knownIp; + uint8_t knownBrightness = 0; + uint8_t knownMode = 0; + uint8_t knownPalette = 0; + uint8_t tftcharwidth = 19; + + long lastUpdate_mod = 0; + long lastRedraw = 0; +#if DISPLAY_HAS_BACKLIGHT + bool displayTurnedOff = false; +#endif -#define TFT_MOSI 19 -#define TFT_SCLK 18 -#define TFT_CS 5 -#define TFT_DC 16 -#define TFT_RST 23 + // string that are used multiple time (this will save some flash memory) + static const char _name[]; + static const char _enabled[]; -#define TFT_BL 4 // Display backlight control pin -#define ADC_EN 14 // Used for enabling battery voltage measurements - not used in this program +public: -TFT_eSPI tft = TFT_eSPI(135, 240); // Invoke custom library +TTGO_T_DisplayMod() : Usermod() +{} //gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() { - Serial.begin(115200); - Serial.println("Start"); +void setup() override { + if (!ready) { + // Reserve pins. allocateMultiplePins() validates every pin before allocating + // any of them, so a failure here leaves no pins allocated (no manual rollback needed). + // Pins the active TFT_eSPI User_Setup doesn't define (e.g. parallel-bus displays, + // or lines hardwired to GND/3V3) are simply left out of the array below. + const static PinManagerPinType pins[] PROGMEM = { +#ifdef TFT_MISO + { TFT_MISO, INPUT }, +#endif +#ifdef TFT_MOSI + { TFT_MOSI, OUTPUT }, +#endif +#ifdef TFT_SCLK + { TFT_SCLK, OUTPUT }, +#endif +#ifdef TFT_CS + { TFT_CS, OUTPUT }, +#endif +#ifdef TFT_DC + { TFT_DC, OUTPUT }, +#endif +#ifdef TFT_RST + { TFT_RST, OUTPUT }, +#endif +#ifdef TFT_BL + { TFT_BL, OUTPUT }, +#endif +#ifdef TOUCH_CS + { TOUCH_CS, OUTPUT }, +#endif + }; + if (!PinManager::allocateMultiplePins(pins, sizeof(pins)/sizeof(pins[0]), (PinOwner) getId())) { + DEBUG_PRINTLN("TFT: Failed to allocate pins!"); + return; + } + tft.init(); tft.setRotation(3); //Rotation here is set up for the text to be readable with the port on the left. Use 1 to flip. tft.fillScreen(TFT_BLACK); tft.setTextSize(2); + //tft.setTextSize(1); tft.setTextColor(TFT_WHITE); tft.setCursor(1, 10); tft.setTextDatum(MC_DATUM); tft.setTextSize(3); + //tft.setTextSize(1); tft.print("Loading..."); + DEBUG_PRINTLN("TFT Loading..."); - if (TFT_BL > 0) { // TFT_BL has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h +#if DISPLAY_HAS_BACKLIGHT + if (TFT_BL >= 0) { // TFT_BL has been set in the TFT_eSPI library in the User Setup file TTGO_T_Display.h pinMode(TFT_BL, OUTPUT); // Set backlight pin to output mode - digitalWrite(TFT_BL, HIGH); // Turn backlight on. + digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on. } +#endif // tft.setRotation(3); + ready = true; + } } -// gets called every time WiFi is (re-)connected. Initialize own network -// interfaces here -void userConnected() {} - -// needRedraw marks if redraw is required to prevent often redrawing. -bool needRedraw = true; - -// Next variables hold the previous known values to determine if redraw is -// required. -String knownSsid = ""; -IPAddress knownIp; -uint8_t knownBrightness = 0; -uint8_t knownMode = 0; -uint8_t knownPalette = 0; -uint8_t tftcharwidth = 19; // Number of chars that fit on screen with text size set to 2 - -long lastUpdate = 0; -long lastRedraw = 0; -bool displayTurnedOff = false; -// How often we are redrawing screen -#define USER_LOOP_REFRESH_RATE_MS 5000 -void userLoop() { +void loop() override { + if (!ready) return; + + if (!enabled) { +#if DISPLAY_HAS_BACKLIGHT + if (!displayTurnedOff) { + digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); // Turn backlight off. + displayTurnedOff = true; + } +#endif + return; + } // Check if we time interval for redrawing passes. - if (millis() - lastUpdate < USER_LOOP_REFRESH_RATE_MS) { + if (millis() - lastUpdate_mod < USER_LOOP_REFRESH_RATE_MS) { return; } - lastUpdate = millis(); + lastUpdate_mod = millis(); - // Turn off display after 5 minutes with no change. - if(!displayTurnedOff && millis() - lastRedraw > 5*60*1000) { - digitalWrite(TFT_BL, LOW); // Turn backlight off. - displayTurnedOff = true; - } - // Check if values which are shown on display changed from the last time. if (((apActive) ? String(apSSID) : WiFi.SSID()) != knownSsid) { needRedraw = true; @@ -117,29 +157,35 @@ void userLoop() { } if (!needRedraw) { + // Turn off display after 5 minutes with no change. +#if DISPLAY_HAS_BACKLIGHT + if(!displayTurnedOff && millis() - lastRedraw > 5*60*1000) { + digitalWrite(TFT_BL, !TFT_BACKLIGHT_ON); // Turn backlight off. + displayTurnedOff = true; + } +#endif return; } needRedraw = false; +#if DISPLAY_HAS_BACKLIGHT if (displayTurnedOff) { digitalWrite(TFT_BL, TFT_BACKLIGHT_ON); // Turn backlight on. displayTurnedOff = false; } +#endif lastRedraw = millis(); // Update last known values. - #if defined(ESP8266) - knownSsid = apActive ? WiFi.softAPSSID() : WiFi.SSID(); - #else - knownSsid = WiFi.SSID(); - #endif + knownSsid = apActive ? apSSID : WiFi.SSID(); knownIp = apActive ? IPAddress(4, 3, 2, 1) : WiFi.localIP(); knownBrightness = bri; knownMode = strip.getMainSegment().mode; knownPalette = strip.getMainSegment().palette; tft.fillScreen(TFT_BLACK); + //tft.setTextColor(TFT_WHITE); tft.setTextSize(2); // First row with Wifi name tft.setCursor(1, 1); @@ -156,7 +202,6 @@ void userLoop() { // tft.print(apPass); // else // tft.print(knownIp); - if (apActive) { tft.print("AP IP: "); tft.print(knownIp); @@ -189,7 +234,58 @@ void userLoop() { // Fifth row with estimated mA usage tft.setCursor(1, 112); // Print estimated milliamp usage (must specify the LED type in LED prefs for this to be a reasonable estimate). - tft.print(strip.currentMilliamps); - tft.print("mA (estimated)"); - + tft.print(BusManager::currentMilliamps()); + tft.print("mA (estimated)"); } + + +/* + * addToJsonInfo() can be used to add custom entries to the /json/info part of the JSON API. + * Creating an "u" object allows you to add custom key/value pairs to the Info section of the WLED web UI. + * Below it is shown how this could be used for e.g. a light sensor + */ +void addToJsonInfo(JsonObject& root) override { + JsonObject user = root["u"]; + if (user.isNull()) user = root.createNestedObject("u"); + + auto state = user.createNestedArray(FPSTR(_name)); + if (!ready) { + state.add(PSTR("Pin allocation failure")); + return; + } + if (enabled) { +#if DISPLAY_HAS_BACKLIGHT + state.add(displayTurnedOff ? PSTR("Off") : PSTR("On")); +#else + state.add(PSTR("On")); +#endif + } else { + state.add(PSTR("Disabled")); + } +} + + +void addToConfig(JsonObject &root) override { + JsonObject top = root.createNestedObject(FPSTR(_name)); // usermodname + top[FPSTR(_enabled)] = enabled; +} + +bool readFromConfig(JsonObject& root) override +{ + // default settings values could be set here (or below using the 3-argument getJsonValue()) instead of in the class definition or constructor + // setting them inside readFromConfig() is slightly more robust, handling the rare but plausible use case of single value being missing after boot (e.g. if the cfg.json was manually edited and a value was removed) + JsonObject top = root[FPSTR(_name)]; + bool configComplete = !top.isNull(); + + configComplete &= getJsonValue(top[FPSTR(_enabled)], enabled); + return configComplete; +} + +}; // TTGO_T_DisplayMod + +// add more strings here to reduce flash memory usage +const char TTGO_T_DisplayMod::_name[] PROGMEM = "TFT Display"; +const char TTGO_T_DisplayMod::_enabled[] PROGMEM = "enabled"; + +static TTGO_T_DisplayMod usermod_ttgo_t_display; +REGISTER_USERMOD(usermod_ttgo_t_display); diff --git a/wled00/usermod.cpp b/wled00/usermod.cpp index 40fda83b07..60199d09c6 100644 --- a/wled00/usermod.cpp +++ b/wled00/usermod.cpp @@ -11,19 +11,19 @@ //Use userVar0 and userVar1 (API calls &U0=,&U1=, uint16_t) //gets called once at boot. Do all initialization that doesn't depend on network here -void userSetup() +__attribute__((weak)) void userSetup() { } //gets called every time WiFi is (re-)connected. Initialize own network interfaces here -void userConnected() +__attribute__((weak)) void userConnected() { } //loop. You can use "if (WLED_CONNECTED)" to check for successful connection -void userLoop() +__attribute__((weak)) void userLoop() { }