[{"content":"Home Assistant — Setting Up for Advanced Projects Read This First The projects on this site involve editing Home Assistant configuration files, installing apps, and working with ESPHome devices. None of it is particularly difficult — but there are a handful of things you need to understand and set up before you start, or you will eventually break something and not know how to fix it.\nThis page covers everything. Do it once, do it properly, and the rest becomes straightforward.\n1. Backups — Set These Up Before Anything Else This is the single most important thing on this page. If you break your HA configuration and have no backup, you are starting from scratch. If you have a backup from last night, you are five minutes from a working system.\nEnable Automatic Nightly Backups Go to Settings → System → Backups Click the three-dot menu in the top right Select Automatic backups Enable automatic backups, set the schedule to daily, and set how many to keep — 7 is reasonable HA will now create a full backup of your entire installation every night including all configuration files, apps, and integrations.\nLocal vs External Backups Backups stored on the same device running HA are useful but not sufficient on their own. If the SD card or SSD fails completely, your backups go with it.\nAn external backup is more valuable than a local one. Here is why: if something goes badly wrong and you do not know how to fix it, you can wipe the device completely, do a fresh HA install, and restore from your external backup. The whole process takes around 20 minutes and you end up with a perfectly working system as it was the previous night. This has saved at least one of us from a very bad day.\nOptions for external backup:\nCopy manually to a USB drive or NAS periodically via the Samba Share app Google Drive Backup — a community app that automatically copies backups to Google Drive on a schedule If you do nothing else from this page, set up an automatic external backup.\nHow to Restore a Backup If something goes wrong:\nGo to Settings → System → Backups Select the backup you want to restore Click Restore Choose full restore or restore specific parts HA will restore and restart A full restore takes a few minutes. When it comes back up it will be exactly as it was when the backup was taken. For a complete reinstall and restore from scratch, the HA documentation covers the recovery interface that is accessible even when the main UI is unavailable.\n2. Essential Apps Apps are additional software that runs alongside Home Assistant. Go to Settings → Apps to find and install them.\nFile Editor You need a way to edit configuration files. File Editor is a simple browser-based text editor — it opens configuration files, shows a green tick at the top when the syntax is valid, and a red exclamation mark when there is an error. It is the right tool for the projects on this site.\nTo install: Go to Settings → Apps, search for File editor, click it and click Install. Enable Show in sidebar so it appears in the left menu. Click Start.\nAdvanced users may prefer Studio Code Server which provides a full VS Code editor experience with more features, but File Editor is sufficient for everything here and considerably less intimidating.\nESPHome Device Builder Required for all ESP32 projects. This app compiles and flashes ESPHome firmware to your devices, and manages the connection between ESPHome devices and HA.\nTo install: Go to Settings → Apps, search for ESPHome, click ESPHome Device Builder and install it. Enable Show in sidebar.\nMosquitto Broker Required if you want to use MQTT — the messaging protocol used to pass data between devices.\nTo install: Go to Settings → Apps, search for Mosquitto broker and install it. Once installed go to Settings → Devices \u0026amp; Services where it should appear as a discovered integration — configure it there.\nFor MQTT connections from ESPHome devices and tools like MQTT Explorer, use your HA username and password as the MQTT credentials.\nSamba Share Allows you to access HA files from your computer as a network drive. Useful for copying backups to an external location and for accessing configuration files from a desktop editor.\nTo install: Go to Settings → Apps, search for Samba share and install it. On the Configuration tab set a username and password, save, and start the app. The HA file system will appear as a network share at \\\\homeassistant.local\\ on Windows or smb://homeassistant.local/ on Mac.\n3. Understanding the File Structure Home Assistant configuration lives in /config/. The files you will edit most often are:\nFile Purpose configuration.yaml Main HA configuration — sensors, automations, templates secrets.yaml Passwords and sensitive values — kept separate from main config automations.yaml Automations (usually managed via the UI) You should never need to edit anything outside /config/ for the projects on this site.\n4. secrets.yaml — Keep Passwords Out of Config Files Any password, API key, or sensitive value should go in secrets.yaml, not directly in configuration.yaml. This means if you share your config files or paste them somewhere for help, your passwords are not exposed.\nsecrets.yaml lives in /config/ alongside configuration.yaml. It is simple key-value pairs:\nwifi_ssid: \u0026#34;YourNetworkName\u0026#34; wifi_password: \u0026#34;YourWifiPassword\u0026#34; mqtt_username: \u0026#34;yourusername\u0026#34; mqtt_password: \u0026#34;yourpassword\u0026#34; In any other config file reference them with !secret:\nwifi: ssid: !secret wifi_ssid password: !secret wifi_password Create secrets.yaml once and add to it as needed. Never paste its contents anywhere public.\n5. Editing configuration.yaml Safely This is where most people get into trouble. configuration.yaml uses YAML format — a structured text format where indentation is critical. One wrong space and HA will refuse to start.\nThe good news is there are safety nets built in. Use them every single time.\nOpen the File Click File editor in the left sidebar. Click the folder icon in the top left, navigate to configuration.yaml and open it.\nYAML Rules You Must Know Indentation uses spaces, never tabs. Two spaces per level is standard.\nIndentation defines structure. This is valid:\nsensor: - platform: template name: \u0026#34;My Sensor\u0026#34; This is broken — name is indented one level too far:\nsensor: - platform: template name: \u0026#34;My Sensor\u0026#34; Colons need a space after them. name: \u0026quot;value\u0026quot; is correct. name:\u0026quot;value\u0026quot; will fail.\nLists use a dash and a space. - item not -item.\nThe Green Tick File Editor shows a green tick at the top of the screen when the file syntax is valid, and a red exclamation mark when there is an error. Always wait for the green tick before saving. If you see a red mark there is a syntax error somewhere — find and fix it before saving.\nValidate Before Restarting After saving configuration.yaml, always validate it before restarting HA:\nGo to Developer Tools — the \u0026lt;/\u0026gt; icon in the left sidebar, installed by default Click the YAML tab Click Check Configuration If valid you will see:\nConfiguration valid! If there is an error it will tell you exactly which file and line the problem is on. Fix it, save, and check again before restarting.\nNever restart HA without checking the configuration first. It takes ten seconds and saves enormous amounts of grief.\nRestart Safely Once the configuration check passes, click Restart in Developer Tools → YAML.\nFor minor changes such as template sensors or automations you can use the specific reload buttons rather than a full restart — faster and less disruptive.\nWhat to Do When It Breaks If HA fails to start after a configuration change:\nThe web UI may still be partially accessible even if HA core failed to load — try accessing it Go to Settings → System → Logs to find the exact error Open configuration.yaml in File Editor and fix the problem on the line indicated Check the configuration again in Developer Tools Restart Nine times out of ten the problem is a missing space, an extra space, or a tab where there should be a space. The error message points directly at the line.\nIf you cannot access the web UI at all, restore last night\u0026rsquo;s backup.\n6. Developer Tools Developer Tools is installed by default in every HA installation — look for the \u0026lt;/\u0026gt; icon at the bottom of the left sidebar.\nTab What it does States Shows the current state of every entity — invaluable for debugging Actions Lets you manually trigger any HA action Template Test Jinja2 template code interactively before putting it in config files YAML Check configuration and reload specific components Statistics Shows long-term statistics data States tab — if a sensor is not working, search for its entity ID here first. If it does not appear, the device is not connected to HA. If it shows unknown or unavailable, the device is connected but not reporting data yet.\nTemplate tab — test any template expression here and see the result immediately before putting it into configuration.yaml. This saves many unnecessary restart cycles.\n7. ESPHome — First Time Setup ESPHome is the firmware framework used for all the ESP32 projects on this site. Getting the first device set up can trip people up — here is the process.\nFirst Flash — Use the Web Installer The very first time you flash a new ESP32 you need a USB cable and a Chrome or Edge browser (other browsers do not support the Web Serial API required for USB flashing).\nGo to https://web.esphome.io and follow the prompts to install a basic ESPHome firmware via USB. This gets the device onto your WiFi network for the first time.\nCommon problems at this stage:\nWrong USB cable — many USB cables are charge-only with no data lines. If the browser does not detect the device, try a different cable. Wrong COM port selected — if you have multiple devices connected, select the one that appears when you plug in the ESP32. Boot mode — some ESP32 boards need to be manually placed in boot mode before flashing. Try holding the BOOT button while clicking flash and releasing after upload starts. If that does not work, use this sequence: hold BOOT, keep it held, press and release RESET, then release BOOT. The board is now in boot mode and ready to flash. After First Flash — Use the ESPHome App Once the device is on your WiFi network, all subsequent flashes happen wirelessly (OTA — Over The Air) from the ESPHome app in HA. No USB cable needed.\nIn the ESPHome app, the device will appear as Discovered. Click Adopt to bring it under ESPHome management and create a yaml configuration file for it.\nThe yaml Configuration File Every ESPHome device has a yaml file that defines everything about it — sensors, display, buttons, MQTT settings. The projects on this site provide complete yaml files you can use as a starting point.\nEdit the yaml in the ESPHome app, click Install → Wireless to flash it over WiFi. The device will reboot with the new firmware.\nKeeping Device Names Permanent Once HA has discovered an ESPHome device and started recording its sensor data, do not change the device name in the yaml. Sensor history is stored against entity IDs derived from the device name. Renaming it creates new entities with no history.\n8. MQTT Explorer MQTT Explorer is a free desktop application for Mac, Windows, and Linux that connects to your MQTT broker and shows all topics and messages in real time.\nDownload from: https://mqtt-explorer.com\nConnecting to Your HA Broker Create a new connection with these settings:\nField Value Protocol mqtt:// Host homeassistant.local or your HA IP address Port 1883 Username Your HA username Password Your HA password What to Use It For Verifying that ESPHome devices are publishing data correctly Checking sensor values in real time Finding and deleting stale retained discovery messages when you rename or remove devices Retained messages are shown with an R indicator. To delete one, click the topic, clear the value field, ensure the retain flag is set, and publish an empty message. This removes it from the broker permanently.\n9. The HA Log When something is wrong, the log tells you what. Check it after any significant change.\nSettings → System → Logs\nErrors appear in red, warnings in orange. Most warnings are harmless — focus on errors. The log shows the exact component and line causing the problem.\nDownload the full log file using the download icon in the top right — useful when asking for help.\n10. Keeping Things Tidy A few habits that prevent problems accumulating:\nCheck the log after every new device. A fresh device should add no new errors to the log. Fix them immediately — errors accumulate and become hard to untangle months later.\nGive everything a unique name. Never give two ESPHome devices a sensor, button, or light with the same name. Generic names like \u0026quot;Restart\u0026quot; or \u0026quot;Display Backlight\u0026quot; cause duplicate entity ID errors when MQTT is involved.\nUse secrets.yaml from the start. Retrofitting it later is tedious.\nNever pull the power to restart HA. Always use the software restart. Pulling power mid-write can corrupt the database.\nKeep purge_keep_days at a sensible value. The default is 10 days which is too short. 90 days is a good balance between database size and useful history. Long-term statistics are stored separately and never purged regardless of this setting.\nYou Are Ready With backups running, the essential apps installed, and an understanding of how to edit configuration files safely, you are ready to tackle the projects on this site.\nWhen something goes wrong — and it will occasionally — you have the tools to diagnose and fix it, and a backup to fall back on.\nQuestions or Problems If anything on this site is unclear, incorrect, or missing a step that left you stuck, please get in touch. These guides are written from real experience and improved based on feedback.\nContact: fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/ha-foundations/","summary":"\u003ch1 id=\"home-assistant--setting-up-for-advanced-projects\"\u003eHome Assistant — Setting Up for Advanced Projects\u003c/h1\u003e\n\u003ch2 id=\"read-this-first\"\u003eRead This First\u003c/h2\u003e\n\u003cp\u003eThe projects on this site involve editing Home Assistant configuration files, installing apps, and working with ESPHome devices. None of it is particularly difficult — but there are a handful of things you need to understand and set up before you start, or you will eventually break something and not know how to fix it.\u003c/p\u003e\n\u003cp\u003eThis page covers everything. Do it once, do it properly, and the rest becomes straightforward.\u003c/p\u003e","title":"Home Assistant — Setting Up for Advanced Projects"},{"content":"Victron MPPT Solar Monitoring via ESPHome BLE — No Cloud Required Overview Most Victron MPPT tutorials either use the official VRM cloud platform or a USB-to-serial cable. This guide shows a completely different approach: reading Victron SmartSolar MPPT 100/20 data directly via Bluetooth Low Energy (BLE) using an ESP32 and ESPHome, feeding it into Home Assistant entirely locally — no Victron account, no cloud, no cables required.\nThe result is a real-time solar monitoring system that stores full historical data in Home Assistant and works even when the internet is down.\nWhat you\u0026rsquo;ll get:\nPV Power (W) Battery Voltage (V) Battery Current (A) Load Current (A) Yield Today (kWh) Cumulative Solar Energy Produced (Wh) — with full historical statistics MPPT State (Off / Bulk / Absorption / Float) MPPT Error and Fault status Hardware Required Any ESP32 board with Bluetooth — ESP32-S3 recommended for range and stability Victron SmartSolar MPPT with Bluetooth enabled — most models from 2018 onwards Nothing else — no cables, no adaptors, no Victron dongle The ESP32 can be placed anywhere within Bluetooth range of the MPPT — typically 5-10 metres through walls.\nHow It Works Victron SmartSolar MPPTs broadcast encrypted BLE advertisements continuously. The ESPHome victron_ble external component decrypts these using a device-specific bindkey, extracts the sensor data, and sends it to Home Assistant via the native ESPHome API.\nThe key point is that the MPPT broadcasts constantly — the ESP32 listens passively, decrypts, and forwards. No Bluetooth pairing required, no connection established.\nStep 1: Get Your Bindkey and MAC Address The bindkey is a 32-character hex encryption key unique to your MPPT. You need it to decrypt the BLE data.\nVia the Victron Connect app:\nOpen Victron Connect on your phone Connect to your MPPT Go to Product Info Find \u0026ldquo;Encryption key\u0026rdquo; and copy the 32-character hex string Alternative — Python method (if the key is not visible in the app):\nSome firmware versions do not expose the key in the app. On a computer with Bluetooth and Python:\npip install victron-ble python -m victron_ble scan This scans for nearby Victron BLE devices and displays their data including the encryption key.\nYou also need the MAC address of your MPPT — visible in Victron Connect under Product Info, or shown during the Python scan.\nStep 2: Basic ESPHome Configuration — API Only This is the simplest working configuration. The native ESPHome API handles everything — Home Assistant discovers all sensors automatically with no additional setup.\nesphome: name: victron-reader # Choose a name — do not change it later (see Gotchas) friendly_name: Victron Reader min_version: 2024.11.0 name_add_mac_suffix: false esp32: board: esp32-s3-devkitc-1 # Adjust for your specific board framework: type: esp-idf logger: api: # Native ESPHome API — all that is needed for HA ota: - platform: esphome wifi: ssid: !secret wifi_ssid password: !secret wifi_password bluetooth_proxy: active: true # Optional but useful — extends BLE range for HA button: - platform: restart name: \u0026#34;Victron Reader Restart\u0026#34; # Always use a unique descriptive name here external_components: - source: github://Fabian-Schmidt/esphome-victron_ble esp32_ble_tracker: victron_ble: - id: MySmartSolar mac_address: \u0026#34;XX:XX:XX:XX:XX:XX\u0026#34; # Your MPPT MAC address bindkey: \u0026#34;xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx\u0026#34; # Your 32-character bindkey sensor: - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;Battery Voltage\u0026#34; type: BATTERY_VOLTAGE - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;Battery Current\u0026#34; type: BATTERY_CURRENT - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;Yield Today\u0026#34; type: YIELD_TODAY - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;PV Power\u0026#34; id: pv_power type: PV_POWER - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;Load Current\u0026#34; type: LOAD_CURRENT # Integrates PV Power over time to give cumulative energy produced - platform: integration name: \u0026#34;Solar Energy Produced\u0026#34; sensor: pv_power unit_of_measurement: \u0026#34;Wh\u0026#34; time_unit: h accuracy_decimals: 2 state_class: total_increasing device_class: energy # WiFi signal in dBm - platform: wifi_signal name: \u0026#34;Victron Reader WiFi Signal\u0026#34; update_interval: 60s # WiFi signal as percentage # device_class must be overridden — % is not valid for signal_strength (see Gotchas) - platform: wifi_signal name: \u0026#34;Victron Reader WiFi Signal Percent\u0026#34; update_interval: 60s device_class: \u0026#34;\u0026#34; filters: - lambda: return min(max(2 * (x + 100.0), 0.0), 100.0); unit_of_measurement: \u0026#34;%\u0026#34; binary_sensor: - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;MPPT is in Fault state\u0026#34; type: DEVICE_STATE_FAULT - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;MPPT has Error\u0026#34; type: CHARGER_ERROR - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;MPPT in FLOAT\u0026#34; type: DEVICE_STATE_FLOAT text_sensor: - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;MPPT state\u0026#34; type: DEVICE_STATE - platform: victron_ble victron_ble_id: MySmartSolar name: \u0026#34;MPPT Error reason\u0026#34; type: CHARGER_ERROR - platform: wifi_info ip_address: name: \u0026#34;Victron Reader IP\u0026#34; ssid: name: \u0026#34;Victron Reader SSID\u0026#34; bssid: name: \u0026#34;Victron Reader BSSID\u0026#34; mac_address: name: \u0026#34;Victron Reader MAC\u0026#34; Flash this to your ESP32, add the device in Home Assistant under Settings → Devices \u0026amp; Services → ESPHome, and all sensors will appear automatically.\nStep 3: Optional — Add MQTT for Multi-Device Publishing If you want other ESPHome devices (such as display boards) to subscribe to the Victron data directly without going through Home Assistant, add MQTT to the configuration.\nThis is not required for basic HA monitoring — only add it if you have a specific reason such as feeding data to ESPHome displays.\nAdd this section to your yaml:\nmqtt: broker: !secret mqtt_broker username: !secret mqtt_username password: !secret mqtt_password discovery_unique_id_generator: mac # Essential — prevents duplicate entity ID errors Other ESPHome devices can then subscribe to topics like:\nvictron-reader/sensor/pv_power/state victron-reader/sensor/battery_voltage/state victron-reader/sensor/yield_today/state victron-reader/sensor/mppt_state/state Where victron-reader matches your ESPHome device name.\nStep 4: Utility Meters for Daily/Monthly/Yearly Tracking The Solar Energy Produced sensor accumulates total Wh produced since the device was first flashed. To track daily, monthly and yearly yield add utility meters to configuration.yaml:\nutility_meter: solar_daily: source: sensor.victron_reader_solar_energy_produced cycle: daily solar_monthly: source: sensor.victron_reader_solar_energy_produced cycle: monthly solar_yearly: source: sensor.victron_reader_solar_energy_produced cycle: yearly Adjust the entity ID to match your device name. These reset automatically at midnight, month end, and year end.\nStep 5: Historical Top 10 Solar Days Dashboard Card Because Solar Energy Produced has state_class: total_increasing, Home Assistant writes hourly statistics to the statistics database table which is never purged regardless of your recorder settings. This means you can query years of historical data directly from the SQLite database.\nCreate the Python script at /config/solar_top10.py:\nimport sqlite3 db = sqlite3.connect(\u0026#39;/config/home-assistant_v2.db\u0026#39;) rows = db.execute(\u0026#39;\u0026#39;\u0026#39; SELECT DATE(start_ts,\u0026#39;unixepoch\u0026#39;,\u0026#39;localtime\u0026#39;), ROUND((MAX(sum)-MIN(sum))/1000,3) FROM statistics JOIN statistics_meta ON statistics.metadata_id=statistics_meta.id WHERE statistic_id=\u0026#39;sensor.victron_reader_solar_energy_produced\u0026#39; AND sum IS NOT NULL GROUP BY DATE(start_ts,\u0026#39;unixepoch\u0026#39;,\u0026#39;localtime\u0026#39;) ORDER BY (MAX(sum)-MIN(sum)) DESC LIMIT 10 \u0026#39;\u0026#39;\u0026#39;).fetchall() print(\u0026#39;;\u0026#39;.join(r[0]+\u0026#39;|\u0026#39;+str(r[1])+\u0026#39; kWh\u0026#39; for r in rows)) Adjust the statistic_id to match your actual entity ID.\nAdd a command_line sensor to configuration.yaml:\ncommand_line: - sensor: name: \u0026#34;Solar Top 10\u0026#34; command: \u0026#34;python3 /config/solar_top10.py\u0026#34; scan_interval: 3600 value_template: \u0026#34;{{ value }}\u0026#34; Add a markdown card to your dashboard:\ntype: markdown title: \u0026#34;☀️ Top 10 Solar Days\u0026#34; content: \u0026gt; {% set data = states(\u0026#39;sensor.solar_top_10\u0026#39;) %} {% if data and data != \u0026#39;unknown\u0026#39; %} {% set rows = data.split(\u0026#39;;\u0026#39;) %} | # | Date | kWh | |---|------|-----| {% for row in rows %} {% set parts = row.split(\u0026#39;|\u0026#39;) %} | {{ loop.index }} | {{ parts[0] }} | {{ parts[1] }} | {% endfor %} {% else %} Loading... {% endif %} Note: Home Assistant converts spaces to underscores in entity IDs. \u0026ldquo;Solar Top 10\u0026rdquo; becomes sensor.solar_top_10.\nDashboard Examples Daily yield versus total load as a bar chart using the built-in statistics-graph card:\nchart_type: bar period: day type: statistics-graph entities: - entity: sensor.esphome_web_78378c_yield_today name: Total Yield - entity: sensor.total_load_energy name: Total Load stat_types: - change grid_options: columns: full logarithmic_scale: false Gotchas and Common Mistakes Do not change the ESPHome device name Once Home Assistant has discovered the device and started recording statistics, never change the ESPHome device name. Statistics are stored against the entity ID which is derived from the device name. Renaming creates a new entity with no history — the old data still exists in the database under the old name but is no longer attached to the current entity.\nWiFi signal percent device_class error ESPHome automatically assigns device_class: signal_strength to all wifi_signal platform sensors. This device class only accepts dBm or dB units — not percent. Without the device_class: \u0026quot;\u0026quot; override on the percent sensor, Home Assistant will log this error on every restart:\nEntity is using native unit of measurement \u0026#39;%\u0026#39; which is not a valid unit for the device class \u0026#39;signal_strength\u0026#39; Fix: add device_class: \u0026quot;\u0026quot; to the percent sensor as shown in the config above.\nAlways give restart buttons a unique name If you have multiple ESPHome devices all using name: \u0026quot;Restart\u0026quot; on their restart button, MQTT discovery generates duplicate entity IDs and HA logs errors on every restart. Always give restart buttons a unique device-specific name such as \u0026quot;Victron Reader Restart\u0026quot;.\nIf using MQTT — always add discovery_unique_id_generator: mac Without it, ESPHome generates short generic IDs like ESPsensorpv_power which clash with other devices. The mac generator prefixes every ID with the device MAC address making them globally unique.\nStale retained MQTT messages MQTT discovery messages are published with the retain flag. If you rename sensors or remove them, the old discovery messages remain in the broker and replay every time HA restarts causing errors. Use MQTT Explorer (free desktop app) to view and delete stale retained messages under the homeassistant/ topic tree.\nStatistics entity ID mismatch after device rename or migration If you rename the ESPHome device or migrate from a different integration, the entity IDs change. HA treats the new entity as brand new — previous statistics are orphaned under the old ID. You can verify what historical data exists by querying the statistics_meta table directly via SSH:\nsqlite3 /config/home-assistant_v2.db \\ \u0026#34;SELECT statistic_id, DATE(MIN(start_ts),\u0026#39;unixepoch\u0026#39;,\u0026#39;localtime\u0026#39;) as earliest \\ FROM statistics \\ JOIN statistics_meta ON statistics.metadata_id=statistics_meta.id \\ WHERE statistic_id LIKE \u0026#39;%solar%\u0026#39; \\ GROUP BY statistic_id;\u0026#34; Entities Created in Home Assistant Entity Type Description sensor.X_pv_power sensor Live PV power in W sensor.X_battery_voltage sensor Battery voltage in V sensor.X_battery_current sensor Battery current in A sensor.X_load_current sensor Load current in A sensor.X_yield_today sensor kWh produced today, resets at midnight sensor.X_solar_energy_produced sensor Cumulative Wh, never resets sensor.X_mppt_state text sensor Off / Bulk / Absorption / Float binary_sensor.X_mppt_in_float binary True when battery full binary_sensor.X_mppt_has_error binary True if MPPT error present binary_sensor.X_mppt_is_in_fault_state binary True if MPPT fault present Where X is your ESPHome device friendly name with spaces replaced by underscores.\nTested With Victron SmartSolar MPPT 100/20 ESP32-S3-DevKitC-1 ESPHome 2026.3.x Home Assistant 2026.4.x Credits ESPHome external component by Fabian-Schmidt\n✉ fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/victron-ble-esphome/","summary":"\u003ch1 id=\"victron-mppt-solar-monitoring-via-esphome-ble--no-cloud-required\"\u003eVictron MPPT Solar Monitoring via ESPHome BLE — No Cloud Required\u003c/h1\u003e\n\u003cp\u003e\u003cimg alt=\"Victron SmartSolar MPPT 100/20 with LiFePO4 battery and solar panel fuse board\" loading=\"lazy\" src=\"/posts/victron-ble-esphome/mppt.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eMost Victron MPPT tutorials either use the official VRM cloud platform or a USB-to-serial cable. This guide shows a completely different approach: reading Victron SmartSolar MPPT 100/20 data directly via Bluetooth Low Energy (BLE) using an ESP32 and ESPHome, feeding it into Home Assistant entirely locally — no Victron account, no cloud, no cables required.\u003c/p\u003e","title":"Victron MPPT Solar Monitoring via ESPHome BLE"},{"content":"Calibrating a Humidity Sensor with Saturated Salt Solution — ASTM E104 Method Why Calibrate? The music room contains a pair of B\u0026amp;W Matrix 800 loudspeakers — the original flagship model from 1987. At 110kg each, with crossovers, drive units, and cabinet materials that are no longer manufactured, they are effectively irreplaceable. Keeping them in good condition over decades requires stable humidity. Too dry and surrounds crack, spiders stiffen, and wooden cabinet joints open. Too damp and cones absorb moisture, crossover components corrode, ferrofluid in the tweeters degrades. And most importantly with this pair, the nickel plated magnet assemblies oxidise, binding the voice coils in the coil gap. This has already killed one of the very rare drivers.\nThe humidity monitoring in this house — the import risk automation, the absolute humidity calculations, the window-opening decisions — exists primarily to protect these speakers. That means the sensors doing the measuring need to be accurate. A 6% error in relative humidity is not a minor calibration footnote, it is the difference between correct and incorrect humidity management decisions.\nMost humidity sensors ship with a stated accuracy — the Sensirion SHT45, for example, specifies ±1.8% RH typical. But \u0026ldquo;typical\u0026rdquo; means the population average. Any individual sensor may sit anywhere within that band, and without calibration you do not know where yours sits.\nFor casual temperature monitoring that does not matter. For tracking absolute humidity differences between rooms, calculating moisture import risk, or using a sensor as a reference standard to calibrate others, it matters considerably.\nThe method described here — the saturated salt solution technique — is the same one used by Vaisala, the Met Office, and professional calibration laboratories. It costs almost nothing and requires no specialist equipment.\nThe Physics When sodium chloride (table salt, NaCl) is dissolved in water to saturation — meaning excess undissolved crystals are present — the air immediately above the solution reaches a fixed equilibrium relative humidity determined only by temperature. This is a consequence of the thermodynamic water activity of the saturated solution.\nAt 15°C the equilibrium RH above saturated NaCl is 75.61%. This value is well established and tabulated in scientific literature going back to Greenspan (1977), which remains the reference cited by ASTM E104 and by Vaisala\u0026rsquo;s calibration documentation.\nThe temperature coefficient is small — approximately 0.047% RH per °C — which means the target shifts by less than 0.5% across the typical indoor temperature range of 10-20°C. This is why NaCl is particularly useful as a calibration reference: it is stable, cheap, and not strongly temperature-sensitive.\nA sealed container with saturated NaCl paste on the bottom will equilibrate to 75.61% (at 15°C) and hold that humidity indefinitely as long as excess undissolved salt remains present.\nThe Failed Attempt The first attempt used a 2-litre plastic Tupperware container, standard supermarket table salt, and the wires fed through a gap in the side sealed loosely with blu-tack.\nIt failed for two reasons. Table salt contains sodium ferrocyanide as an anti-caking agent — this changes the water activity of the solution and shifts the equilibrium RH away from the theoretical value. Plastic is also slightly porous and can absorb or release moisture, and the large volume took much longer to equilibrate. After several hours the sensor was reading 72.10% at 14.5°C against a theoretical target of 75.64% — a 3.54% discrepancy that was still drifting.\nThe Correct Setup The working setup addresses each failure point:\n500ml glass jar — non-porous, does not absorb or release moisture, seals well 99.9% pure NaCl — no anti-caking agent, no additives Distilled water — no dissolved minerals Dupont wires through a hole in the lid, sealed with hot glue — airtight SHT45 sensor suspended ~4cm above the salt surface — not touching walls or paste ESP32-C6 board outside the jar — board heat cannot warm the chamber air Salt paste preparation: 20g pure NaCl mixed with approximately 7ml distilled water. The correct consistency is wet sand with visible undissolved crystals throughout — if it looks clear and fully dissolved, add more dry salt. Excess undissolved salt is essential; the equilibrium only holds as long as the solution remains saturated.\nESPHome Wiring The SHT45 connects to the ESP32-C6 via I2C:\nSHT45 VCC → 3V3 SHT45 GND → GND SHT45 SDA → GPIO4 SHT45 SCL → GPIO5 Basic ESPHome yaml for the SHT45:\ni2c: sda: GPIO4 scl: GPIO5 sensor: - platform: sht4x temperature: name: \u0026#34;Temperature SHT45\u0026#34; humidity: name: \u0026#34;Humidity SHT45\u0026#34; update_interval: 60s For calibration work, temporarily increase update_interval to 60s or more to minimise self-heating from the sensor element. Self-heating is the enemy in a sealed chamber — even a fraction of a degree of sensor-induced warming shifts the equilibrium target and skews the result.\nThe Calibration Run With the chamber sealed, the sensor climbs rapidly toward the equilibrium value then slows as it approaches it — a classic asymptotic curve. At 15°C the target is 75.61%.\nThe curve takes several hours to fully flatten. The ESP32 board outside the jar matters — if the board is inside, its heat warms the chamber air, raises the temperature, and lowers the equilibrium target, giving a false reading. With the board outside, the chamber temperature tracks the room.\nAfter full equilibration at 14.70°C (NaCl equilibrium: 75.63%), the SHT45 stabilised at approximately 74.5%, giving an offset of +1.1%. This is well within the sensor\u0026rsquo;s ±1.8% specified tolerance and confirms the SHT45 is performing correctly — it simply sits near the low end of its accuracy band.\nApplying the Offset The offset is applied as an ESPHome filter, correcting the value at source before Home Assistant sees it:\nsensor: - platform: sht4x temperature: name: \u0026#34;Temperature SHT45\u0026#34; humidity: name: \u0026#34;Humidity SHT45\u0026#34; filters: - offset: 1.1 update_interval: 60s Flash and the sensor now reports calibrated values. The AirGradient I-9PSL indoor unit (which uses a Sensirion SHT40) was subsequently compared against the calibrated SHT45 and found to read approximately 6% high — a significant error that would meaningfully affect any humidity-based calculations such as absolute humidity or import risk.\nThe Deviance Template Sensor To monitor calibration stability over time, a template sensor in configuration.yaml tracks how far the SHT45 reads from the theoretical NaCl equilibrium, with live temperature compensation:\ntemplate: - sensor: - name: \u0026#34;SHT45 RH Deviance\u0026#34; unit_of_measurement: \u0026#34;%\u0026#34; state: \u0026gt; {% set t = states(\u0026#39;sensor.esp32_c6_zero_1_sht45_temperature_sht45\u0026#39;) | float(0) %} {% set target = 75.61 + (15.0 - t) * 0.047 %} {{ (target - states(\u0026#39;sensor.esp32_c6_zero_1_sht45_humidity_sht45\u0026#39;) | float(0)) | round(2) }} When the sensor is placed back in the chamber, this should read near zero if calibration is holding. When deployed in normal room air it will show a large positive value — which is expected and correct, since room humidity is well below 75%.\nThe Calibration Chain This setup establishes a three-tier calibration chain:\nPrimary reference — saturated NaCl solution (ASTM E104 method), traceable to established thermodynamic constants Transfer standard — SHT45 #1, calibrated against the primary reference, stored sealed when not in use Working sensors — two additional SHT45s on order, to be calibrated against the transfer standard The transfer standard should only be used for calibrating other sensors — not deployed permanently. Store it in a sealed glass jar at moderate humidity (40-60% RH) with a small silica gel packet. Label the jar with the sensor ID, offset, calibration date, and method.\nThis is precisely the chain used by professional calibration laboratories, scaled to a kitchen table.\n✉ fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/sht45-calibration/","summary":"\u003ch1 id=\"calibrating-a-humidity-sensor-with-saturated-salt-solution--astm-e104-method\"\u003eCalibrating a Humidity Sensor with Saturated Salt Solution — ASTM E104 Method\u003c/h1\u003e\n\u003ch2 id=\"why-calibrate\"\u003eWhy Calibrate?\u003c/h2\u003e\n\u003cp\u003eThe music room contains a pair of B\u0026amp;W Matrix 800 loudspeakers — the original flagship model from 1987. At 110kg each, with crossovers, drive units, and cabinet materials that are no longer manufactured, they are effectively irreplaceable. Keeping them in good condition over decades requires stable humidity. Too dry and surrounds crack, spiders stiffen, and wooden cabinet joints open. Too damp and cones absorb moisture, crossover components corrode, ferrofluid in the tweeters degrades. And most importantly with this pair, the nickel plated magnet assemblies oxidise, binding the voice coils in the coil gap. This has already killed one of the very rare drivers.\u003c/p\u003e","title":"Calibrating a Humidity Sensor with Saturated Salt Solution — ASTM E104 Method"},{"content":"Reviving a Weather Underground Station with Home Assistant and ESPHome Station IBRIGHTO3 had been dormant for years. Reviving it required no dedicated weather station hardware — just sensors already deployed around the house, a few template conversions in Home Assistant, and a REST command firing every five minutes.\nHardware The station currently reports three parameters:\nTemperature and humidity — AirGradient O-1PS outdoor sensor (SHT40), reporting via MQTT to HA Barometric pressure — DPS310 sensor on an ESP32-C6, integrated via ESPHome native API The DPS310 is a good choice for pressure — it is a high-resolution MEMS barometer with better temperature compensation than the commonly used BME280, and ESPHome has native support for it.\nHow Weather Underground PWS Upload Works WU accepts data from personal weather stations via a simple HTTP GET request to their legacy upload endpoint. No library required — just a correctly formatted URL with your station credentials and sensor values appended as query parameters.\nWU expects all values in imperial units:\nTemperature in °F Pressure in inHg (not hPa) Dewpoint in °F Humidity in % All conversions are handled in HA Jinja2 templates at upload time, so the sensors themselves can report in metric as normal.\nSetting Up Your Weather Underground Station If you do not already have a WU account and station registered:\nCreate an account at wunderground.com Go to My Profile → My Devices → Add New Device Register your station — you will need an approximate location and elevation Note your Station ID (e.g. IBRIGHTO3) and Station Key — the key acts as a password for uploads Keep the station key out of your git repository and out of published yaml. Store it in secrets.yaml — see the foundations page if you have not set that up.\nHA Configuration rest_command Add this to configuration.yaml, substituting your own station ID and key:\nrest_command: weather_underground: url: \u0026#34;https://weatherstation.wunderground.com/weatherstation/updateweatherstation.php?ID=YOUR_STATION_ID\u0026amp;PASSWORD=YOUR_STATION_KEY\u0026amp;dateutc=now\u0026amp;tempf={{ tempf }}\u0026amp;humidity={{ humidity }}\u0026amp;baromin={{ baromin }}\u0026amp;dewptf={{ dewptf }}\u0026amp;action=updateraw\u0026#34; method: GET Automation This fires every five minutes and assembles all the converted values before calling the REST command. The dewpoint uses the Magnus-Alduchov formula — more accurate than the simplified approximation commonly found online:\nalias: Weather Underground Update triggers: - minutes: /5 trigger: time_pattern actions: - data: tempf: \u0026gt;- {{ ((states(\u0026#39;sensor.o_1ps_outdoors_air_temperature\u0026#39;) | float * 9/5) + 32) | round(1) }} humidity: \u0026#34;{{ states(\u0026#39;sensor.o_1ps_outdoors_air_humidity\u0026#39;) | float | round(1) }}\u0026#34; baromin: \u0026gt;- {{ (states(\u0026#39;sensor.esp32_c6_zero_2_dps310_pressure_dps310\u0026#39;) | float * 0.02953) | round(2) }} dewptf: \u0026gt;- {{ ((243.04 * ((states(\u0026#39;sensor.o_1ps_outdoors_air_humidity\u0026#39;) | float / 100) | log + (17.625 * (states(\u0026#39;sensor.o_1ps_outdoors_air_temperature\u0026#39;) | float)) / (243.04 + (states(\u0026#39;sensor.o_1ps_outdoors_air_temperature\u0026#39;) | float))) / (17.625 - ((states(\u0026#39;sensor.o_1ps_outdoors_air_humidity\u0026#39;) | float / 100) | log + (17.625 * (states(\u0026#39;sensor.o_1ps_outdoors_air_temperature\u0026#39;) | float)) / (243.04 + (states(\u0026#39;sensor.o_1ps_outdoors_air_temperature\u0026#39;) | float)))) * 9/5) + 32) | round(1) }} action: rest_command.weather_underground Adjust the entity IDs to match your own sensors.\nUnit Conversions Parameter Sensor unit WU unit Conversion Temperature °C °F (C × 9/5) + 32 Pressure hPa inHg hPa × 0.02953 Humidity % % none Dewpoint calculated °F Magnus-Alduchov → °F The Magnus-Alduchov formula for dewpoint in °C before the imperial conversion:\nTd = 243.04 × (ln(RH/100) + 17.625T/(243.04+T)) / (17.625 − (ln(RH/100) + 17.625T/(243.04+T))) Where T is temperature in °C and RH is relative humidity in %. The Jinja2 log filter computes the natural logarithm, which is what the formula requires.\nVerifying Uploads Once the automation is running, check the WU dashboard for your station — it should show \u0026ldquo;Online (updated X minutes ago)\u0026rdquo; within the first update cycle. The WunderMap will show your station alongside neighbouring stations with current conditions.\nIf uploads are not appearing, check the HA logbook for the automation and look for errors on the rest_command.weather_underground call. A 400 response usually means a malformed URL or incorrect station credentials. A blank response with no error typically means the station key is correct but the station ID was not found.\nPlanned Upgrades The current sensor complement covers the basics well. Two upgrades are planned:\nSHT45 in a proper outdoor enclosure — the AirGradient O-1PS will be replaced with a calibrated SHT45 in a Stevenson screen-style enclosure. The SHT45 is Sensirion\u0026rsquo;s highest-accuracy humidity sensor and has been calibrated against a saturated NaCl reference standard (ASTM E104 method) — see the SHT45 calibration post for that process. This will improve humidity and temperature accuracy meaningfully.\nHydreon RG-15 optical rain gauge — the RG-15 reports rainfall accumulation in mm and intensity in mm/hr via UART, which is exactly what WU expects. Unlike tipping bucket gauges it has no moving parts and is self-cleaning. ESPHome has community support for the Hydreon series. Rain is currently the only gap in the station\u0026rsquo;s sensor set — wind and UV are not practical at this location.\n✉ fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/wu/","summary":"\u003ch1 id=\"reviving-a-weather-underground-station-with-home-assistant-and-esphome\"\u003eReviving a Weather Underground Station with Home Assistant and ESPHome\u003c/h1\u003e\n\u003cp\u003e\u003cimg alt=\"Weather Underground dashboard for station IBRIGHTO3 showing live temperature, dewpoint, humidity and pressure for Hove\" loading=\"lazy\" src=\"/posts/wu/wu-dashboard.jpg\"\u003e\u003c/p\u003e\n\u003cp\u003eStation \u003ca href=\"https://www.wunderground.com/dashboard/pws/IBRIGHTO3\"\u003eIBRIGHTO3\u003c/a\u003e had been dormant for years. Reviving it required no dedicated weather station hardware — just sensors already deployed around the house, a few template conversions in Home Assistant, and a REST command firing every five minutes.\u003c/p\u003e\n\u003chr\u003e\n\u003ch2 id=\"hardware\"\u003eHardware\u003c/h2\u003e\n\u003cp\u003eThe station currently reports three parameters:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003e\u003cstrong\u003eTemperature and humidity\u003c/strong\u003e — AirGradient O-1PS outdoor sensor (SHT40), reporting via MQTT to HA\u003c/li\u003e\n\u003cli\u003e\u003cstrong\u003eBarometric pressure\u003c/strong\u003e — DPS310 sensor on an ESP32-C6, integrated via ESPHome native API\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe DPS310 is a good choice for pressure — it is a high-resolution MEMS barometer with better temperature compensation than the commonly used BME280, and ESPHome has native support for it.\u003c/p\u003e","title":"Reviving a Weather Underground Station with Home Assistant and ESPHome"},{"content":"Running a Home Server and Network Stack from 12V Solar Overview The home automation and server infrastructure here runs directly from the 12V LiFePO4 solar battery bank rather than from mains via inverter. This keeps the critical systems online during grid outages and eliminates inverter losses for loads that do not need 240V AC.\nThe load list:\nSynology NAS — file server, HA backups, Plex, email server, web server — running 24/7 for 13 years WiFi 7 router ADSL modem Raspberry Pi 5 (Home Assistant) Amazon Echo Several ESP32 sensor and display boards Motion-activated lighting throughout the house The office — Mac Mini, two screens, active speakers at around 140W — runs from the Victron Phoenix 12/375 pure sine inverter. In summer this runs most days from solar. In winter it is an occasional load.\nThe solar array is approximately 300W in a partially shaded dense urban setting — not rooftop. Panel placement is a compromise dictated by available locations rather than optimal orientation.\nFusing Everything is fused. The battery feeds an automotive/marine blade fuse box with around 8 fused outputs, each protected with an appropriately rated blade fuse for the load it serves. The fuse box has a cover. This is not optional — a short circuit on an unfused LiFePO4 system can deliver thousands of amps and start a fire instantly.\nThe wiring path is: LiFePO4 battery → fuse box → DC-DC converters → loads.\nThe Problem with LiFePO4 Voltage Swing A 12V LiFePO4 battery does not sit at a constant 12V. Under charge it reaches 14.4-14.6V. Under moderate discharge it sits around 13.2-13.4V. The BMS cuts off at around 10V but the discharge curve is so steep at that point it is only minutes of runtime. In practice the system is set to trigger mains top-up charging at 11V — well above the BMS cutoff.\nAny equipment powered directly from the battery sees that entire range. Routers, NAS units, and modems with cheap internal regulators can behave unpredictably at the top of the charge range, and some protection circuits trip at low voltage before the battery is actually depleted.\nThe solution is regulated DC-DC conversion between the battery and the loads.\nThe Core Converter: Buck-Boost 8-40V to 12V The workhorse of the 12V rail is a DC-DC buck-boost converter rated 8-40V input, 12V/3A output, 36W — around £19 on Amazon.\nThis is the right tool for a LiFePO4 installation because it is a true buck-boost — it can both step down and step up, meaning it maintains a stable 12.2V output across the full battery voltage range from charged to nearly depleted. A pure buck converter would fail to regulate once the battery drops below its output setpoint.\nThis single converter has been running continuously for over a year powering:\nSynology NAS via barrel connector WiFi 7 router via barrel connector ADSL modem via barrel connector Amazon Echo via barrel connector All four devices see a stable 12.2V regardless of battery state. The converter handles the full swing of LiFePO4 charge/discharge without complaint.\nRaspberry Pi 5: The USB-PD Problem The Pi 5 is more demanding than its predecessors. It uses USB Power Delivery negotiation and expects a compliant 5V/5A PD source. Feed it a plain 5V buck converter output and it boots — but logs a low-power warning and limits the USB ports to 600mA instead of the full 1.6A. For a headless HA server with several USB devices this matters.\nThe converter used is an XY-3606 buck converter, 9-36V to 5.2V, 6A — around £6 on Amazon. Solid, efficient, handles the full battery voltage range without issue.\nThe PD problem is solved with a one-line config fix. Add this to /boot/firmware/config.txt on the Pi:\nusb_max_current_enable=1 This tells the Pi to skip the PD handshake and assume full current is available. The warning disappears and USB ports operate at full current.\nAccessing the Boot Partition on HAOS This is the fiddly part. Home Assistant OS runs from a read-only boot partition that is not accessible from within HA itself — not via the file editor, not via the SSH add-on.\nThe only reliable way to edit config.txt is to remove the storage device (USB SSD or SD card), mount it on another computer, edit the file on the FAT32 boot partition, and reinstall it. On a Mac the boot partition mounts automatically in Finder when you plug in the drive.\nIt is a one-time operation — once the line is in config.txt it survives reboots and HA updates indefinitely.\nESP32 Boards The ESP32 sensor and display boards run from 5V USB. At 150-250mA per board the total current draw is modest. A USB hub powered from the XY-3606 output handles them cleanly.\nWinter Top-Up: Economy 7 Automation In winter the solar array may not fully cover the 12V loads. When the battery drops below 11V overnight, a Home Assistant automation triggers the Victron BSC IP67 12/17 mains charger on the Economy 7 off-peak tariff (00:30-07:30 GMT, 15.02p/kWh). The charger runs until the battery is full or the off-peak window ends.\nEach time it triggers, the charger tops up approximately 600Wh — roughly one day of 12V load. Over an entire winter this happens around 15-20 times in total. From spring through autumn it essentially never happens — solar covers everything.\nBoth the Victron BSC charger and the Phoenix inverter talk to Home Assistant via BLE, giving full visibility of charge state, current, and energy flows on the dashboard.\nLoad and Yield Logging All power flows are monitored in Home Assistant:\nSolar yield — from the Victron SmartSolar MPPT 100/20 via BLE (see the Victron BLE monitoring post) Battery voltage — live from the same MPPT feed House load — via a Sonoff energy monitoring plug, supplemented by derived sensors Monthly totals — utility meters tracking yield and consumption The daily yield vs load bar chart gives an immediate picture of whether the solar system is covering the 12V loads or drawing from the charger overnight.\nPower Budget The 12V infrastructure draws roughly:\nDevice Typical draw Synology NAS (idle) ~15W WiFi 7 router ~8W ADSL modem ~4W Raspberry Pi 5 ~5W Amazon Echo ~3W ESP32 boards (×6) ~6W Lighting (motion, standby) ~2W Total ~43W At 43W continuous the 200Ah LiFePO4 bank (approximately 1.9kWh usable) covers around 44 hours of runtime without any solar input. In practice the system has never depleted the battery to the automation trigger point in summer. In winter the E7 automation handles the occasional overnight shortfall.\nThe server delivering this page is part of that same 12V infrastructure — solar powered all year round.\n✉ fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/12v/","summary":"\u003ch1 id=\"running-a-home-server-and-network-stack-from-12v-solar\"\u003eRunning a Home Server and Network Stack from 12V Solar\u003c/h1\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eThe home automation and server infrastructure here runs directly from the 12V LiFePO4 solar battery bank rather than from mains via inverter. This keeps the critical systems online during grid outages and eliminates inverter losses for loads that do not need 240V AC.\u003c/p\u003e\n\u003cp\u003eThe load list:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eSynology NAS — file server, HA backups, Plex, email server, web server — running 24/7 for 13 years\u003c/li\u003e\n\u003cli\u003eWiFi 7 router\u003c/li\u003e\n\u003cli\u003eADSL modem\u003c/li\u003e\n\u003cli\u003eRaspberry Pi 5 (Home Assistant)\u003c/li\u003e\n\u003cli\u003eAmazon Echo\u003c/li\u003e\n\u003cli\u003eSeveral ESP32 sensor and display boards\u003c/li\u003e\n\u003cli\u003eMotion-activated lighting throughout the house\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe office — Mac Mini, two screens, active speakers at around 140W — runs from the Victron Phoenix 12/375 pure sine inverter. In summer this runs most days from solar. In winter it is an occasional load.\u003c/p\u003e","title":"Running a Home Server and Network Stack from 12V Solar"},{"content":"\nWhat Is It? The board sold on AliExpress as \u0026ldquo;ESP32 Arduino LVGL WIFI\u0026amp;Bluetooth Development Board 2.8\u0026quot; 240×320 Smart Display Screen with Touch WROOM\u0026rdquo; has become something of a community favourite. It is widely known as the CYD — Cheap Yellow Display — named for its yellow PCB. For under £10 delivered it gives you:\nESP32-WROOM-32 (dual core, 240MHz, 4MB flash) ILI9341 2.8\u0026quot; TFT LCD, 240×320 pixels, 16-bit colour XPT2046 resistive touchscreen RGB LED LDR light sensor SD card slot USB-C programming and power The display quality is genuinely impressive for the price. It ships running an LVGL demo that shows what the hardware is capable of. ESPHome support is solid and well documented.\nIf you have not set up secrets.yaml, the ESPHome addon, or MQTT yet, read the foundations page first — this post assumes that groundwork is in place.\nFlashing with ESPHome No soldering required. The board programs directly over USB-C.\nPlug the board into your Mac via USB-C Open Chrome and go to web.esphome.io Click Connect, select the serial port (appears as a CP210x or CH340 device) Click Prepare for first use — this installs a minimal ESPHome firmware Your Home Assistant ESPHome addon will discover the device automatically From that point on, all updates are OTA If the board does not appear as a serial port, hold the BOOT button while plugging in to force programming mode.\nBacklight The backlight is driven via PWM on GPIO21, giving brightness control from HA. Define an output and wire it to a monochromatic light entity:\noutput: - platform: ledc pin: GPIO21 id: backlight_output light: - platform: monochromatic output: backlight_output name: \u0026#34;Backlight Yellow\u0026#34; id: backlight restore_mode: ALWAYS_ON This creates a light entity in HA so you can dim the display from automations — useful for turning it down at night.\nSPI and Display The ILI9341 pinout for this board is fixed — do not change these pins:\nspi: - id: tft clk_pin: GPIO14 mosi_pin: GPIO13 display: - platform: mipi_spi model: ILI9341 spi_id: tft cs_pin: GPIO15 dc_pin: GPIO2 invert_colors: false auto_clear_enabled: true rotation: 90 dimensions: width: 320 height: 240 ESPHome 2026.4.0 breaking change: The ili9xxx platform was replaced by mipi_spi. If you are on an earlier config you will also need to remove color_palette: 8BIT and miso_pin from the spi: block — neither is valid under the new platform.\nrotation: 90 puts the display in landscape mode — 320 wide, 240 tall. With auto_clear_enabled: true ESPHome redraws the full screen on each update cycle so you do not need to manually clear it in the lambda.\nFonts ESPHome can pull fonts directly from Google Fonts with no local installation needed:\nfont: - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_small size: 16 - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_medium size: 26 - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_large size: 36 The id is how you reference each font in the display lambda. size is in pixels. On a 240px tall display in landscape, size 36 gives a large header row and size 26 works well for data rows. Size 16 is useful for secondary labels.\nPositioning uses it.printf(x, y, font, colour, format, value) where x and y are pixel coordinates from the top-left corner. Each row of font_medium (size 26) occupies roughly 28-30 pixels vertically — increment y by that amount between rows. Colours are Color(R, G, B) with values 0–255.\nGetting Data In This display pulls from two sources — MQTT for the Victron solar data, and the HA API for everything else.\nThe Victron MPPT publishes directly to MQTT via the ESPHome BLE reader (covered in the Victron post). Reading it directly from MQTT rather than going via HA is faster and removes a dependency. For sensors coming from HA, use the homeassistant platform:\n- platform: homeassistant name: \u0026#34;House Power\u0026#34; id: house_power entity_id: sensor.sonoff_1002263266_power The id is what you use to reference the sensor value in the display lambda. MQTT credentials go in secrets.yaml — see the foundations page if you have not set that up.\nThe Display Lambda The lambda runs on every display refresh cycle. it.fill(Color(0, 0, 0)) clears the screen to black at the start. Text is drawn with it.printf() using the same format string syntax as C — %.0f for an integer float, %.2f for two decimal places, %s for a string. The %% produces a literal % character.\nHorizontal divider lines are drawn with it.horizontal_line(x, y, width, colour). The import risk sign logic uses a ternary to prepend + to positive values:\nit.printf(0, 282, id(font_medium), Color(255, 100, 100), \u0026#34;RISK %s%.2f\u0026#34;, id(import_risk).state \u0026gt;= 0 ? \u0026#34;+\u0026#34; : \u0026#34;\u0026#34;, id(import_risk).state); Complete YAML esphome: name: esphome-web-b301e8 friendly_name: Yellow min_version: 2025.11.0 name_add_mac_suffix: false esp32: board: esp32dev framework: type: arduino logger: api: ota: - platform: esphome wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap: ssid: \u0026#34;CYD Fallback\u0026#34; password: !secret fallback_password captive_portal: mqtt: broker: !secret mqtt_broker username: !secret mqtt_username password: !secret mqtt_password discovery_unique_id_generator: mac button: - platform: restart name: \u0026#34;Restart Yellow\u0026#34; output: - platform: ledc pin: GPIO21 id: backlight_output light: - platform: monochromatic output: backlight_output name: \u0026#34;Backlight Yellow\u0026#34; id: backlight restore_mode: ALWAYS_ON sensor: - platform: mqtt_subscribe name: \u0026#34;PV Power\u0026#34; id: pv_power topic: esphome-web-78378c/sensor/pv_power/state unit_of_measurement: \u0026#34;W\u0026#34; - platform: mqtt_subscribe name: \u0026#34;Battery Voltage\u0026#34; id: battery_voltage topic: esphome-web-78378c/sensor/battery_voltage/state unit_of_measurement: \u0026#34;V\u0026#34; - platform: mqtt_subscribe name: \u0026#34;Yield Today\u0026#34; id: yield_today topic: esphome-web-78378c/sensor/yield_today/state unit_of_measurement: \u0026#34;kWh\u0026#34; - platform: homeassistant name: \u0026#34;House Power\u0026#34; id: house_power entity_id: sensor.sonoff_1002263266_power - platform: homeassistant name: \u0026#34;Total Load Power\u0026#34; id: total_load_power entity_id: sensor.total_load_power - platform: homeassistant name: \u0026#34;Total Load Monthly\u0026#34; id: total_load_monthly entity_id: sensor.total_load_monthly - platform: homeassistant name: \u0026#34;Solar Monthly\u0026#34; id: solar_monthly entity_id: sensor.esphome_web_78378c_spare_solar_monthly - platform: homeassistant name: \u0026#34;Speaker RH\u0026#34; id: speaker_rh entity_id: sensor.rh_speaker_humidity - platform: homeassistant name: \u0026#34;Indoor RH\u0026#34; id: indoor_rh entity_id: sensor.esp32_c6_zero_1_sht45_humidity_sht45 - platform: homeassistant name: \u0026#34;Outdoor RH\u0026#34; id: outdoor_rh entity_id: sensor.o_1ps_outdoors_air_humidity - platform: homeassistant name: \u0026#34;Import Risk\u0026#34; id: import_risk entity_id: sensor.humidity_import_risk text_sensor: - platform: mqtt_subscribe name: \u0026#34;MPPT State\u0026#34; id: mppt_state topic: esphome-web-78378c/sensor/mppt_state/state spi: - id: tft clk_pin: GPIO14 mosi_pin: GPIO13 font: - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_small size: 16 - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_medium size: 26 - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_large size: 36 display: - platform: mipi_spi model: ILI9341 spi_id: tft cs_pin: GPIO15 dc_pin: GPIO2 invert_colors: false auto_clear_enabled: true rotation: 90 dimensions: width: 320 height: 240 lambda: |- it.fill(Color(0, 0, 0)); // Solar section it.printf(0, 0, id(font_large), Color(255, 255, 255), \u0026#34;%.0fW\u0026#34;, id(pv_power).state); it.printf(120, 0, id(font_large), Color(255, 255, 255), \u0026#34;%.2fV\u0026#34;, id(battery_voltage).state); it.printf(0, 38, id(font_medium), Color(100, 100, 255), \u0026#34;Yield %.3f kWh d\u0026#34;, id(yield_today).state); it.printf(0, 66, id(font_medium), Color(100, 100, 255), \u0026#34;Yield %.3f kWh mo\u0026#34;, id(solar_monthly).state); it.printf(0, 93, id(font_medium), Color(255, 255, 255), \u0026#34;State %s\u0026#34;, id(mppt_state).state.c_str()); // Divider it.horizontal_line(0, 120, 320, Color(60, 60, 60)); // Power section it.printf(0, 118, id(font_medium), Color(255, 180, 0), \u0026#34;House Mains %.0fW\u0026#34;, id(house_power).state); it.printf(0, 144, id(font_medium), Color(255, 255, 255), \u0026#34;Inverter %.0fW\u0026#34;, id(total_load_power).state); it.printf(0, 173, id(font_medium), Color(255, 255, 255), \u0026#34;Load %.2f kWh mo\u0026#34;, id(total_load_monthly).state); // Divider it.horizontal_line(0, 202, 320, Color(60, 60, 60)); // Humidity section it.printf(0, 202, id(font_medium), Color(0, 255, 200), \u0026#34;MR %.2f%%\u0026#34;, id(indoor_rh).state); it.printf(0, 228, id(font_medium), Color(100, 200, 255), \u0026#34;OUT %.2f%%\u0026#34;, id(outdoor_rh).state); it.printf(0, 254, id(font_medium), Color(0, 200, 100), \u0026#34;SPK %.2f%%\u0026#34;, id(speaker_rh).state); it.printf(0, 282, id(font_medium), Color(255, 100, 100), \u0026#34;RISK %s%.2f\u0026#34;, id(import_risk).state \u0026gt;= 0 ? \u0026#34;+\u0026#34; : \u0026#34;\u0026#34;, id(import_risk).state); ✉ fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/cyd-yellow-display/","summary":"\u003cp\u003e\u003cimg alt=\"Yellow display showing live solar and humidity data\" loading=\"lazy\" src=\"/posts/cyd-yellow-display/yellow.jpg\"\u003e\u003c/p\u003e\n\u003ch2 id=\"what-is-it\"\u003eWhat Is It?\u003c/h2\u003e\n\u003cp\u003eThe board sold on AliExpress as \u003cem\u003e\u0026ldquo;ESP32 Arduino LVGL WIFI\u0026amp;Bluetooth Development Board 2.8\u0026quot; 240×320 Smart Display Screen with Touch WROOM\u0026rdquo;\u003c/em\u003e has become something of a community favourite. It is widely known as the \u003cstrong\u003eCYD — Cheap Yellow Display\u003c/strong\u003e — named for its yellow PCB. For under £10 delivered it gives you:\u003c/p\u003e\n\u003cul\u003e\n\u003cli\u003eESP32-WROOM-32 (dual core, 240MHz, 4MB flash)\u003c/li\u003e\n\u003cli\u003eILI9341 2.8\u0026quot; TFT LCD, 240×320 pixels, 16-bit colour\u003c/li\u003e\n\u003cli\u003eXPT2046 resistive touchscreen\u003c/li\u003e\n\u003cli\u003eRGB LED\u003c/li\u003e\n\u003cli\u003eLDR light sensor\u003c/li\u003e\n\u003cli\u003eSD card slot\u003c/li\u003e\n\u003cli\u003eUSB-C programming and power\u003c/li\u003e\n\u003c/ul\u003e\n\u003cp\u003eThe display quality is genuinely impressive for the price. It ships running an LVGL demo that shows what the hardware is capable of. ESPHome support is solid and well documented.\u003c/p\u003e","title":"CYD — Cheap Yellow Display with ESPHome"},{"content":"Going Local: Migrating Sonoff Zigbee from eWeLink Cloud to Z2M Using Zigbee2MQTT (Z2M) for fully local Zigbee control The Problem with Cloud Zigbee Running Sonoff Zigbee devices through eWeLink and Home Assistant means every automation trigger passes through Sonoff\u0026rsquo;s servers and back. It works well enough under normal conditions, but introduces a dependency on a third-party cloud that is entirely outside your control. When that cloud has problems, your automations stop responding.\nGoing fully local eliminates that dependency permanently.\nHardware The coordinator is a Sonoff Zigbee 3.0 USB Dongle Plus (ZBDongle-P), based on the Texas Instruments CC2652 chip. It is widely regarded as one of the most reliable coordinators available for Z2M and is well supported across firmware versions.\nFor mesh coverage, four USB Zigbee repeaters from AliExpress — marketed as \u0026ldquo;ZigBee 3.0 Signal Repeater USB Signal Amplifier Extender\u0026rdquo; — at around £3.90 each. These are covered further in the mesh section below.\nThe USB Extension Cable Problem Plugging the dongle directly into a Raspberry Pi causes problems. The Pi\u0026rsquo;s USB 3.0 ports and any attached SSD generate RF interference in the 2.4GHz band — exactly where Zigbee operates. The dongle will function but range and reliability will be degraded.\nThe fix is straightforward: use a USB 2.0 extension cable (use the black ports, not the blue USB 3.0 ones) and position the dongle at least a metre away from the Pi. The antenna should point vertically. The difference in mesh quality is significant.\nWhy Z2M Rather Than ZHA ZHA (Zigbee Home Automation) is the built-in HA integration and works adequately. Zigbee2MQTT offers considerably more:\nPer-device configuration — reporting intervals, sensitivity, and detailed parameters for each device individually LQI (link quality indicator) visible for every device, sortable and filterable Network map showing actual mesh routing topology in real time Watchdog — set to 300 seconds so Z2M restarts automatically if it stalls, rather than sitting dead until you notice The network map alone justifies the migration for diagnostic purposes.\nThe Migration Process The previous setup was two Sonoff Zigbee Bridge and Bridge Pro hubs feeding eWeLink cloud feeding Home Assistant — thirteen devices in total including motion sensors, temperature/humidity sensors, smart plugs, and wall switches.\nThere is no shortcut. Zigbee devices can only belong to one network at a time. The process is:\nUnpair every device from eWeLink Delete the eWeLink integration instances from HA Install the Sonoff dongle and set up Zigbee2MQTT in HA Re-pair all devices to the new coordinator Budget an evening. The Z2M setup itself is straightforward — the time is in re-pairing devices one by one.\nIf you have not set up secrets.yaml or the ESPHome addon yet, see the foundations page first.\nBuilding the Mesh Victorian brick construction is hostile to 2.4GHz. A single solid brick wall produces a significant signal quality drop. The initial mesh after migration was routing poorly — devices several floors away were taking three hops through the roof space rather than routing sensibly through intermediate floors.\nTwo things to understand about Zigbee mesh:\nMains-powered devices route; battery-powered devices do not. Smart plugs, switches, and repeaters act as mesh routers automatically. Battery-powered sensors are end nodes only and contribute nothing to the mesh infrastructure.\nThe mesh takes 24-48 hours to settle after any significant change. Signal quality reported immediately after a device joins is optimistic — routes stabilise over time. Do not draw conclusions about mesh quality in the first few hours.\nRepeaters placed strategically — one on the Synology NAS USB port (on 24/7, no additional power draw), one in the kitchen extension, one on the landing, one in the front room — resolved the routing problems entirely. A previously unreachable outdoor sensor rejoined once the repeaters were in place.\nNetwork Map and External Visualisers The built-in Z2M network map (Network → forceDirected2d) shows live mesh topology — which devices are routing through which, with LQI values on each link. Green links indicate good signal, red indicates poor.\nZ2M also supports external map renderers via the map type dropdown:\nBoth Graphviz and PlantUML produce cleaner, more structured diagrams than the interactive view — useful for documenting a large mesh or identifying routing problems more clearly. Both are worth exploring once the mesh is stable.\nWiFi and Zigbee Channel Separation WiFi and Zigbee share the 2.4GHz band. Overlapping channels produce interference.\nSet WiFi to channel 1 at 20MHz bandwidth. Channel 1 at 20MHz occupies 2.401–2.423GHz. The 20MHz width matters — 40MHz doubles the occupied spectrum and can overlap Zigbee directly. Speed loss on 2.4GHz is not a concern; put fast devices on 5GHz, which Zigbee never touches.\nSet Zigbee to channel 25. In Z2M: Settings → Network Settings → Channel → 25. Channel 25 sits at 2.475GHz, providing over 50MHz of clear separation from WiFi channel 1. Avoid channel 26 — some devices do not support it.\nWhen the Zigbee channel is changed, all devices drop off and rejoin automatically. Most recover within minutes. The mesh re-settles over 24-48 hours. Do it once during initial setup and leave it.\nNeighbours\u0026rsquo; WiFi on channel 11 (2.462GHz) overlaps Zigbee channels 18-22, which is an additional reason to push Zigbee up to channel 25.\nDevice Notes Older Sonoff Zigbee smart sockets contribute weakly to the mesh. The newer round Sonoff sockets are considerably better as routers. Older Sonoff temperature/humidity sensors are unreliable; the current generation is a different product and performs well.\nThe AliExpress repeaters outperformed several of the Sonoff devices as mesh routers. At under £4 each there is no reason to be conservative with them.\nThe Result With eWeLink cloud there was a perceptible lag between a button press and an automation firing — the round trip to Sonoff\u0026rsquo;s servers is measurable. With a fully local Z2M setup the response is essentially instantaneous. 21 devices, solid mesh, sub-perceptible response times.\n✉ fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/zigbee-z2m/","summary":"\u003ch1 id=\"going-local-migrating-sonoff-zigbee-from-ewelink-cloud-to-z2m\"\u003eGoing Local: Migrating Sonoff Zigbee from eWeLink Cloud to Z2M\u003c/h1\u003e\n\u003ch3 id=\"using-zigbee2mqtt-z2m-for-fully-local-zigbee-control\"\u003eUsing Zigbee2MQTT (Z2M) for fully local Zigbee control\u003c/h3\u003e\n\u003ch2 id=\"the-problem-with-cloud-zigbee\"\u003eThe Problem with Cloud Zigbee\u003c/h2\u003e\n\u003cp\u003eRunning Sonoff Zigbee devices through eWeLink and Home Assistant means every automation trigger passes through Sonoff\u0026rsquo;s servers and back. It works well enough under normal conditions, but introduces a dependency on a third-party cloud that is entirely outside your control. When that cloud has problems, your automations stop responding.\u003c/p\u003e","title":"Going Local: Migrating Sonoff Zigbee from eWeLink Cloud to Z2M"},{"content":"LilyGO TTGO T-Display Solar Monitor with ESPHome Overview The LilyGO TTGO T-Display is a compact ESP32 board with a built-in 1.14\u0026quot; ST7789V colour TFT display. This guide covers flashing it for the first time and configuring it with ESPHome to display live solar and house power data — pulling Victron MPPT figures from MQTT and house consumption from Home Assistant.\nWhat the display shows:\nPV Power (W) — from Victron MPPT via MQTT Battery Voltage (V) — from Victron MPPT via MQTT Yield Today (kWh) — from Victron MPPT via MQTT House Power (W) — from Home Assistant Total Load Power (W) — from Home Assistant Hardware Spec Detail Display 1.14\u0026quot; ST7789V IPS TFT Resolution 135 × 240 Pixel density 260 PPI Interface 4-wire SPI Supply voltage 3.3V SoC ESP32 (dual-core) First Flash The TTGO T-Display needs to be flashed via USB before ESPHome OTA is available.\nConnect the board via USB-C In the ESPHome dashboard, create a new device and paste the configuration below Use Install → Plug into this computer for the first flash Subsequent updates can be done via OTA On some systems you may need the CP210x USB driver. The board appears as a standard serial port.\nESPHome Configuration Secrets: MQTT credentials and WiFi details are stored in secrets.yaml rather than hardcoded. See the Home Assistant foundations guide for how to set this up.\nesphome: name: lilygo friendly_name: lilygo esp32: board: esp32dev framework: type: esp-idf logger: api: ota: - platform: esphome wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap: ssid: \u0026#34;Lilygo Fallback Hotspot\u0026#34; password: !secret lilygo_ap_password mqtt: broker: !secret mqtt_broker username: !secret mqtt_username password: !secret mqtt_password sensor: - platform: mqtt_subscribe name: \u0026#34;PV Power\u0026#34; id: pv_power topic: esphome-web-78378c/sensor/pv_power/state unit_of_measurement: \u0026#34;W\u0026#34; - platform: mqtt_subscribe name: \u0026#34;Battery Voltage\u0026#34; id: battery_voltage topic: esphome-web-78378c/sensor/battery_voltage/state unit_of_measurement: \u0026#34;V\u0026#34; - platform: mqtt_subscribe name: \u0026#34;Yield Today\u0026#34; id: yield_today topic: esphome-web-78378c/sensor/yield_today/state unit_of_measurement: \u0026#34;kWh\u0026#34; - platform: homeassistant name: \u0026#34;House Power\u0026#34; id: house_power entity_id: sensor.sonoff_1002263266_power unit_of_measurement: \u0026#34;W\u0026#34; - platform: homeassistant name: \u0026#34;Total Load Power\u0026#34; id: total_load_power entity_id: sensor.total_load_power unit_of_measurement: \u0026#34;W\u0026#34; spi: clk_pin: GPIO18 mosi_pin: GPIO19 output: - platform: ledc pin: GPIO4 id: backlight_output light: - platform: monochromatic output: backlight_output name: \u0026#34;Display Backlight lilygo\u0026#34; id: backlight restore_mode: ALWAYS_ON display: - platform: mipi_spi model: T-Display cs_pin: GPIO5 dc_pin: GPIO16 reset_pin: GPIO23 invert_colors: true rotation: 0 lambda: |- it.fill(Color(0, 0, 0)); it.printf(0, 0, id(font_big), Color(255, 255, 255), \u0026#34;%.0f W\u0026#34;, id(pv_power).state); it.printf(0, 50, id(font_big), Color(255, 255, 255), \u0026#34;%.2f V\u0026#34;, id(battery_voltage).state); it.printf(0, 106, id(font_value), Color(255, 255, 255), \u0026#34;%.3f kWh\u0026#34;, id(yield_today).state); it.printf(0, 144, id(font_big), Color(255, 255, 255), \u0026#34;%.0f W\u0026#34;, id(house_power).state); it.printf(0, 195, id(font_big), Color(255, 255, 255), \u0026#34;i %.0f W\u0026#34;, id(total_load_power).state); font: - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_label size: 20 - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_value size: 28 - file: \u0026#34;gfonts://Roboto\u0026#34; id: font_big size: 40 button: - platform: restart name: \u0026#34;Restart lilygo\u0026#34; How It Works The Victron MPPT figures (PV power, battery voltage, yield) arrive via MQTT, published by the dedicated Victron BLE reader ESP32 — see the Victron MPPT BLE monitoring post for that side of the setup. The house power sensors come directly from Home Assistant via the native API, so the device needs to be added to HA under Settings → Devices \u0026amp; Services → ESPHome as well.\nThe display lambda runs on every refresh cycle, clears the screen to black, and redraws all five values using Roboto at two sizes — 40pt for the main power and voltage figures, 28pt for yield today which is a smaller number with more decimal places.\nThe MQTT mqtt_subscribe sensors update whenever a new value is published — typically every 30–60 seconds from the Victron reader. The homeassistant platform sensors update in near real time via the ESPHome API connection.\nGotchas ESPHome 2026.4.0 breaking change The ili9xxx platform was removed. Use platform: mipi_spi with model: T-Display instead. The named model handles dimensions and offsets automatically — the explicit dimensions: block with offset_height and offset_width is no longer needed and should be removed.\ninvert_colors: true Without this the display colours are inverted — white text appears black on a white background. This is normal for this panel and is not a wiring fault.\nBoard variant Use board: esp32dev not a TTGO-specific board target. The TTGO-specific targets in ESPHome can cause pin mapping issues; esp32dev with explicit pin assignments works reliably.\nfont_label is defined but unused The configuration defines font_label at 20pt which is not used in the current display lambda. It is left in as a convenience for adding labels if you want to annotate the readings.\n✉ fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/lilygo/","summary":"\u003ch1 id=\"lilygo-ttgo-t-display-solar-monitor-with-esphome\"\u003eLilyGO TTGO T-Display Solar Monitor with ESPHome\u003c/h1\u003e\n\u003cp\u003e\u003cimg alt=\"LilyGO TTGO T-Display showing solar PV power, battery voltage, yield, house power and load\" loading=\"lazy\" src=\"/posts/lilygo/lilygo.JPG\"\u003e\u003c/p\u003e\n\u003ch2 id=\"overview\"\u003eOverview\u003c/h2\u003e\n\u003cp\u003eThe LilyGO TTGO T-Display is a compact ESP32 board with a built-in 1.14\u0026quot; ST7789V colour TFT display. This guide covers flashing it for the first time and configuring it with ESPHome to display live solar and house power data — pulling Victron MPPT figures from MQTT and house consumption from Home Assistant.\u003c/p\u003e","title":"LilyGO TTGO T-Display Solar Monitor with ESPHome"},{"content":"Flashing Sonoff Devices with ESPHome — Local Control, No Cloud Why Flash Them? Sonoff devices come from the factory running eWeLink firmware that requires a cloud connection to function. Your automations go out to Sonoff\u0026rsquo;s servers and come back. If their servers go down, or your internet goes down, your devices stop responding. They also collect data about your usage.\nFlashing with ESPHome replaces the factory firmware with your own. The device connects directly to Home Assistant on your local network — no cloud, no third party servers, no internet required. Response times drop from seconds to milliseconds and you have full control over exactly what the device does.\nThis guide covers three Sonoff devices:\nSonoff SV — low voltage relay board (5-24V DC) — easiest to work with Sonoff S20 — UK mains plug adapter Sonoff S26R2 — EU/UK mains plug adapter — most challenging All three use an ESP8266 chip which ESPHome supports fully.\nWhat You Need Hardware DSD TECH SH-U09C USB to TTL Serial Adapter with FTDI FT232RL — approximately £11.50 on Amazon UK. The FTDI chip is important — cheap CH340 adapters can cause problems. The FT232RL is reliable and well supported on Mac, Windows and Linux. Jumper wires — the easiest approach is to buy a pack of Dupont jumper wires and cut them up. Strip and tin about 4-5mm of wire on one end. This gives you a female Dupont connector to plug into the adapter header and a tinned wire end to solder to the Sonoff pads. Soldering iron and solder A computer with Chrome or Edge browser — other browsers do not support the Web Serial API needed for USB flashing Software Home Assistant with the ESPHome app installed — see the foundations guide if you have not done this yet ESPHome Web at https://web.esphome.io — used twice during the process The FTDI Adapter — Set to 3.3V First The Sonoff devices run at 3.3V. The FT232RL adapter can supply either 5V or 3.3V selected by a jumper on the board. You must set this to 3.3V before connecting to any Sonoff device. Supplying 5V will damage the ESP8266.\nLook for the jumper near the bottom of the board labelled 5V / VCC-3V3 — move it to the 3V3 position. Check this every session — it is easy to knock accidentally.\nSafety — Read This Before Opening Anything Never work on these devices while they are plugged into mains power. During the entire flashing process the device is powered only by the 3.3V from the USB adapter via your computer. Keep the mains plug out of the wall for the entire procedure.\nThe Four Programming Pads All three Sonoff devices have four pads for serial programming:\nPad Purpose VCC or 3V3 3.3V power from the adapter GND Ground TX Transmit — connects to RX on the adapter RX Receive — connects to TX on the adapter TX and RX always cross-connect — Sonoff TX goes to adapter RX, Sonoff RX goes to adapter TX. This trips up almost everyone at least once. If the flash fails, swap these two wires first.\nSoldering Technique Photograph the PCB before you solder anything. The wires will cover the pad labels and you will need to refer back to them.\nTin the pads on the Sonoff PCB first with a small amount of fresh solder. Tin the stripped wire ends too. Then lay the wire over the pad and apply the iron briefly — the solder flows together quickly with good tinned surfaces.\nSolid core wire is easier to work with than stranded — it stays in position. Stranded wire works fine if that is what you have, just tin it well.\nWatch for solder bridges and stray wire strands (Careless Whiskers). A stray strand touching an adjacent pad or component will cause problems. After soldering, inspect carefully and remove any whiskers of wire or solder bridges between pads before proceeding.\nSonoff SV — The Easiest The Sonoff SV is a low voltage relay board for 5-24V DC. It is the most accessible device — no mains wiring to work around, just a bare PCB with clearly labelled pads on the board edge.\nThe pads are labelled GND, RX, TX, 3V3 on the board edge. Solder your four wires and connect to the adapter — remembering to cross TX and RX.\nSonoff S20 The S20 is a UK mains plug adapter. Remove the one screw on the back — the case then splits open. The PCB lifts out and the four programming pads are clearly labelled and well spaced at the bottom of the board.\nThe pads are labelled GND, TX, RX, VCC from left to right. You do not need to lift the PCB out — solder the wires with the board in place in the case.\nSonoff S26R2 — The Most Challenging The S26R2 requires accessing the reverse side of the PCB. To get there you need to bend the mains wires carefully out of the way so the board can be removed and turned over. Take care not to stress the mains connections.\nOnce you have access to the back of the board, the pads are smaller than the S20 and require more careful soldering. The other critical difference: the GND pad is physically separate from the VCC, TX and RX cluster — it sits at a different position on the board and is easy to miss entirely.\nThe orange circles in the photo mark the GND pad on the left and the TX/RX/VCC cluster on the right. Solder carefully to the smaller pads.\nFlashing Process This is a two-stage process. ESPHome Web (web.esphome.io) is used twice — once to install a generic ESPHome bootloader, and once to flash your actual configuration. The reason for this is that the initial web flash does not reliably set up WiFi — you need to flash your own yaml which includes your credentials.\nStage 1 — Initial Flash via ESPHome Web With the Sonoff connected to your adapter but not yet plugged into your computer:\nEnter flash mode:\nHold the button on the Sonoff device (this connects GPIO0 to GND internally) While holding, plug the USB adapter into your computer Keep holding for a second or two then release Some Sonoff devices with this chip revision enter flash mode automatically when connected to a programmer — if the flash proceeds without holding the button, that is normal.\nOpen Chrome or Edge and go to https://web.esphome.io\nClick Connect, select the serial device that corresponds to your FT232RL adapter, and follow the prompts to install ESPHome. When it asks for WiFi credentials, enter them — you may be lucky and the device connects to your WiFi successfully at this point, in which case you can skip Stage 3. However with ESP8266 based devices this step frequently fails to connect despite correct credentials. If the device does not appear online in ESPHome after a minute or two, do not worry — proceed to Stage 2 and 3 regardless.\nKeep the USB adapter plugged in and leave the web.esphome.io page open.\nStage 2 — Create Your Configuration in the ESPHome App In Home Assistant, open the ESPHome app.\nClick Add Device Click Continue Choose Empty Configuration Give the device a name — use something unique and descriptive, you cannot easily change this later The device will appear in the ESPHome dashboard as offline — this is expected Click Edit on the new device and paste in your yaml configuration (see the yaml section below). Save it.\nStage 3 — Download and Flash Your Configuration With your yaml saved, click Install on the device.\nChoose Plug into this computer\nChoose Download project — this compiles your yaml and downloads a .bin firmware file to your computer.\nNow go back to https://web.esphome.io (still open from Stage 1).\nClick Install → Choose file and select the .bin file you just downloaded.\nClick Install and wait for the process to complete.\nThe device will reboot with your yaml configuration, connect to your WiFi, and appear as online in the ESPHome dashboard. From this point all further updates are done wirelessly — no USB cable needed.\nESPHome yaml Configuration Sonoff S20 and S26R2 esphome: name: sonoff-s20-1 # Unique name — do not change after first flash friendly_name: Sonoff S20 1 esp8266: board: esp01_1m logger: api: ota: - platform: esphome wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap: ssid: \u0026#34;S20 Fallback\u0026#34; # Fallback hotspot if WiFi fails password: \u0026#34;changeme123\u0026#34; # Change this to something of your own captive_portal: # Provides a web UI via the fallback hotspot binary_sensor: - platform: gpio pin: number: GPIO0 mode: INPUT_PULLUP inverted: true name: \u0026#34;Button\u0026#34; on_press: - switch.toggle: relay - platform: status name: \u0026#34;Status\u0026#34; # Connectivity sensor in HA switch: - platform: gpio name: \u0026#34;Relay\u0026#34; pin: GPIO12 id: relay restore_mode: RESTORE_DEFAULT_OFF output: - platform: esp8266_pwm id: led pin: number: GPIO13 inverted: true light: - platform: monochromatic name: \u0026#34;LED\u0026#34; output: led internal: true # Hides the LED from HA — it is used internally only The fallback hotspot is important. If the device cannot connect to your WiFi — wrong password, network name changed, router issue — it will create its own hotspot named \u0026ldquo;S20 Fallback\u0026rdquo;. Connect to it from your phone or computer and a captive portal page will appear allowing you to update the WiFi credentials. This means a WiFi misconfiguration never bricks the device.\nSonoff SV esphome: name: sonoff-sv-1 friendly_name: Sonoff SV 1 esp8266: board: esp01_1m logger: api: ota: - platform: esphome wifi: ssid: !secret wifi_ssid password: !secret wifi_password ap: ssid: \u0026#34;SV Fallback\u0026#34; password: \u0026#34;changeme123\u0026#34; captive_portal: binary_sensor: - platform: gpio pin: number: GPIO0 mode: INPUT_PULLUP inverted: true name: \u0026#34;Button\u0026#34; on_press: - switch.toggle: relay - platform: status name: \u0026#34;Status\u0026#34; switch: - platform: gpio name: \u0026#34;Relay\u0026#34; pin: GPIO12 id: relay restore_mode: RESTORE_DEFAULT_OFF output: - platform: esp8266_pwm id: led pin: number: GPIO13 inverted: true light: - platform: monochromatic name: \u0026#34;LED\u0026#34; output: led internal: true After Flashing — Home Assistant Integration Once online, the device appears automatically in Settings → Devices \u0026amp; Services → ESPHome in Home Assistant. You will have a switch entity for the relay and a binary sensor for the button.\nBuild automations, add to dashboards, monitor alongside all your other HA devices — entirely locally with no cloud dependency.\nTips and Lessons Learned Photograph the PCB before soldering — the wires hide the pad labels Tin pads and wires separately first — then join them Check the 3.3V jumper on the adapter before every session TX and RX always cross-connect — if the flash fails, swap these two first Use cut-down Dupont jumper wires — female end plugs into the adapter header The S26R2 GND pad is separate — easy to miss Inspect for solder bridges and stray wire strands before powering up If no serial port appears — try a different USB cable, different USB port, or check your OS has the FTDI driver installed Device name cannot easily be changed — once HA starts recording statistics against an entity ID, renaming creates orphaned data Questions If anything here is unclear or you get stuck, get in touch.\nContact: fletcher@gingineers.com\n","permalink":"https://gingineers.com/posts/sonoff-esphome-flashing/","summary":"\u003ch1 id=\"flashing-sonoff-devices-with-esphome--local-control-no-cloud\"\u003eFlashing Sonoff Devices with ESPHome — Local Control, No Cloud\u003c/h1\u003e\n\u003ch2 id=\"why-flash-them\"\u003eWhy Flash Them?\u003c/h2\u003e\n\u003cp\u003eSonoff devices come from the factory running eWeLink firmware that requires a cloud connection to function. Your automations go out to Sonoff\u0026rsquo;s servers and come back. If their servers go down, or your internet goes down, your devices stop responding. They also collect data about your usage.\u003c/p\u003e\n\u003cp\u003eFlashing with ESPHome replaces the factory firmware with your own. The device connects directly to Home Assistant on your local network — no cloud, no third party servers, no internet required. Response times drop from seconds to milliseconds and you have full control over exactly what the device does.\u003c/p\u003e","title":"Flashing Sonoff Devices with ESPHome — Local Control, No Cloud"}]