summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--_blog/arduino-due.md2
-rw-r--r--_projects/e-reader.md2
-rw-r--r--_projects/etlas.md174
-rw-r--r--_projects/matrix-digital-rain.md2
-rw-r--r--_site/blog/arduino-due/index.html2
-rw-r--r--_site/feed.xml2
-rw-r--r--_site/posts.xml2
-rw-r--r--_site/projects/e-reader/index.html2
-rw-r--r--_site/projects/etlas/index.html160
-rw-r--r--_site/projects/matrix-digital-rain/index.html2
-rw-r--r--_site/robots.txt2
-rw-r--r--_site/sitemap.xml30
12 files changed, 206 insertions, 176 deletions
diff --git a/_blog/arduino-due.md b/_blog/arduino-due.md
index 7c0fb12..57fa381 100644
--- a/_blog/arduino-due.md
+++ b/_blog/arduino-due.md
@@ -112,7 +112,7 @@ address of the flash. This is mandatory for ARM chips unless we relocate the
vector table using the VTOR register.
The first entry of the vector table must be the stack pointer. The stack
-pointer must be initializes to the highest memory location available to
+pointer must be initialized to the highest memory location available to
accommodate the ATSAM3X8E's descending stack.
The second entry of the vector table must be the reset vector. In the reset
diff --git a/_projects/e-reader.md b/_projects/e-reader.md
index 3405cc8..cefbb03 100644
--- a/_projects/e-reader.md
+++ b/_projects/e-reader.md
@@ -89,6 +89,6 @@ As for the prototype, while it's no match for a commercial e-reader with
features like an offline library of books and touch screens, there's something
to be said about reading on hardware you built yourself. You are no longer the
powerless Muggle watching others perform magic. You are the wizard who makes
-the invisible particles swirl into form by whispering C to it.
+the invisible particles swirl into form by whispering C to them.
Files: [source.tar.gz](source.tar.gz)
diff --git a/_projects/etlas.md b/_projects/etlas.md
index 775aa31..e402b05 100644
--- a/_projects/etlas.md
+++ b/_projects/etlas.md
@@ -5,7 +5,10 @@ thumbnail: dash.jpg
layout: post
---
-Etlas is a weather, news, and stock price tracking system.
+Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU
+D1, featuring a 7.5-inch <a href="https://www.waveshare.com/" class="external"
+target="_blank" rel="noopener noreferrer">Waveshare</a> e-paper display and a
+DHT22 sensor module.
<table style="border: none;">
<tr style="border: none;">
@@ -14,89 +17,98 @@ Etlas is a weather, news, and stock price tracking system.
</tr>
</table>
-## Acknowledgements
+The top-left panel shows two weeks of end-of-day prices—the maximum the ESP32's
+SRAM can hold—from the Polygon.io API. The price feed is relayed through a
+FastCGI-wrapped Flask app hosted on a VPS. This lets me configure stock symbols
+in its application settings. The app cycles through them as requests come in
+from the ESP32. Running the Flask app as a FastCGI process while exposing it
+via httpd with htpasswd authentication keeps the server code simple and secure.
-The e-paper display driver in the epd.c file of the project is derived from <a
-href="https://github.com/waveshareteam/e-Paper" class="external"
-target="_blank" rel="noopener noreferrer">Waveshare examples</a> for Arduino
-and STM32 platforms.
-
-The exceptionally elegant algorithm in the file dht.c, which reads DHT22 sensor
-data by comparing consecutive pulses, I ported directly from <a
-href="https://github.com/Fonger/ESP8266-RTOS-DHT" class="external"
-target="_blank" rel="noopener noreferrer">this</a> implementation for ESP8266
-modules to my ESP32. All credit for the algorithm belongs to them.
-
-## Overview
-
-Etlas comprises an embedded system featuring an ESP32 NodeMCU D1
-microcontroller, a 7.5″ <a href="https://www.waveshare.com/" class="external"
-target="_blank" rel="noopener noreferrer">Waveshare</a> e-paper display, a
-DHT22 weather sensor, and a server backend consisting of a FastCGI-wrapped
-Flask app. The following diagram outlines this system architecture.
+The following diagram outlines the Etlas's overall system architecture.
![architecture](etlas_arch.png)
-The embedded application, written in C with the help of the ESP-IDF v5.2.1,
-connects to a (2.4GHz) WiFi network at startup and obtains time from an NTP
-server. It then composites and updates an internal pixel buffer using data
-acquired from various data sources and renders to the screen by writing to its
-Serial Peripheral Interface.
-
-## Price curves
-
-On the e-paper display, the top-left panel displays end-of-day price curves
-over two weeks from the <a href="https://polygon.io/" class="external"
-target="_blank" rel="noopener noreferrer">Polygon.io</a> API. The
-microcontroller's 512KB SRAM limits the price curves to two weeks. Instead of
-using raster images for complex graphics—like e-paper projects often do—Etlas
-computes the price curves from CSV data on the fly using the ESP32's 160MHz
-microprocessor. To avoid the overhead of floating-point arithmetic, all
-computations are performed on integers. The `gui_plot_stocks()` function in the
-gui.c file contains the rendering logic.
-
-Proxying the price feeds through the Flask app permits me to configure the
-tickers I'm interested in via its application settings. The Flask app cycles
-through the tickers in its configuration file as it receives requests from the
-embedded software. The Flask app is secured with basic authentication
-implemented using htpasswd and OpenBSD's httpd web server. This FastCGI-wrapped
-architecture dramatically reduces the complexity of the server code and
-increases system reliability.
-
-## News feed
-
-The more prominent panel on the right displays news from <a
-href="https://www.channelnewsasia.com/" class="external" target="_blank"
-rel="noopener noreferrer">Channel News Asia</a>. The embedded program downloads
-and parses the RSS feed before rendering it to the display. The character
-glyphs used for rendering news are stored in header files in the sprites
-directory as bitmaps. I created the glyphs manually by saving them as images in
-GIMP and rasterizing them with ImageMagick.
-
-I connected the embedded system directly to the RSS feed to write less server
-code (the focus of this project was the embedded system). In hindsight,
-however, it limits the feeds from which Etlas can receive data. In a future
-version, I will relay the RSS feed through the backend (like the stock prices)
-to make it more flexible.
-
-## Weather data
-
-The bottom panels (middle and right) display the temperature and relative
-humidity from a DHT22 sensor. The DHT22 driver, arguably the most interesting
-part of the software, reads real-time sensor data by comparing relative pulse
-widths. The pulses themselves are too quick for the ESP32 to reliably measure
-directly.
-
-Much of the heavy lifting of acquiring, interpreting, and rendering data from
-different data sources is performed on the microcontroller using less than 512
-KB of memory. The embedded software that makes that possible is written in C
-using the ESP-IDF v5.2.1. My e-paper display driver is a port of Waveshare <a
-href="https://github.com/waveshareteam/e-Paper" class="external"
-target="_blank" rel="noopener noreferrer">examples</a> for Arduino
-and STM32 platforms.
-
-I've been using Etlas daily (for a couple of hours on weekdays and all day on
-weekends) since August 2024. As of October 2025, it's been running reliably for
-over a year.
+The more prominent panel on the right of the display shows local and world news
+from Channel NewsAsia. The MCU downloads and parses XML data from the RSS feed
+directly before rendering it to the display. The character glyphs used are
+stored as bitmaps in the sprites directory. I skipped the proxy for news to
+avoid writing more server code, but in hindsight it limits the feeds Etlas can
+handle. I will fix this in a future version.
+
+The middle and bottom right panels display the temperature and relative
+humidity from the DHT22 sensor. The DHT22 uses pulse-width modulation to
+transmit data to the host. The 26µs, 50µs, and 70µs pulses are too fast for the
+ESP32 to measure reliably with standard APIs. Instead, the driver compares
+relative pulse widths to differentiate zeros from ones:
+
+```
+static inline int dht_await_pin_state(int state, int timeout)
+{
+ int t;
+ static const uint16_t delta = 1;
+
+ for (t = 0; t < timeout; t += delta) {
+ ets_delay_us(delta);
+ if (gpio_get_level(DHT_PIN) == state)
+ return t;
+ }
+ return 0;
+}
+
+static inline int dht_get_raw_data(unsigned char buf[DHT_DATA_LEN])
+{
+ int rc;
+ unsigned char i, pwl, pwh;
+
+ gpio_set_level(DHT_PIN, 0);
+ ets_delay_us(1100);
+ gpio_set_level(DHT_PIN, 1);
+
+ if (!dht_await_pin_state(0, 40)) {
+ rc = 1;
+ xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
+ return 0;
+ }
+ if (!dht_await_pin_state(1, 80)) {
+ rc = 2;
+ xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
+ return 0;
+ }
+ if (!dht_await_pin_state(0, 80)) {
+ rc = 3;
+ xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
+ return 0;
+ }
+
+ for (i = 0; i < DHT_DATA_LEN; i++) {
+ if (!(pwl = dht_await_pin_state(1, 50))) {
+ rc = 4;
+ xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
+ return 0;
+ }
+ if (!(pwh = dht_await_pin_state(0, 70))) {
+ rc = 5;
+ xQueueSend(dht_evt_queue, &rc, (TickType_t) 0);
+ return 0;
+ }
+ buf[i] = pwh > pwl;
+ }
+ return 1;
+}
+```
+
+I ported <a href="https://github.com/Fonger/ESP8266-RTOS-DHT" class="external"
+target="_blank" rel="noopener noreferrer">this</a> implementation from ESP8266
+to ESP32—all credit for the algorithm belongs to them.
+
+Etlas is a networked embedded system. All acquisition, processing, and
+rendering of data are performed on the ESP32's 160MHz microprocessor using less
+than 512KB of SRAM. The embedded software that makes this possible is written
+in C using ESP-IDF v5.2.1. The e-paper display driver is derived from Waveshare
+<a href="https://github.com/waveshareteam/e-Paper" class="external"
+target="_blank" rel="noopener noreferrer">examples</a> for Arduino and STM32
+platforms.
+
+Etlas has been running reliably for over a year since August 2024.
Files: [source.tar.gz](source.tar.gz)
diff --git a/_projects/matrix-digital-rain.md b/_projects/matrix-digital-rain.md
index 1b48d82..eb7d330 100644
--- a/_projects/matrix-digital-rain.md
+++ b/_projects/matrix-digital-rain.md
@@ -6,7 +6,7 @@ layout: post
---
"All I see is blonde, brunette, red head." The iconic digital rain from The
-Matrix in C, with zero dependencies - not even ncurses.
+Matrix in C, with zero dependencies—not even ncurses.
<video style="max-width:100%;" controls="" poster="thumb.png">
<source src="matrix.mp4" type="video/mp4">
diff --git a/_site/blog/arduino-due/index.html b/_site/blog/arduino-due/index.html
index fee442f..1d8bba0 100644
--- a/_site/blog/arduino-due/index.html
+++ b/_site/blog/arduino-due/index.html
@@ -145,7 +145,7 @@ address of the flash. This is mandatory for ARM chips unless we relocate the
vector table using the VTOR register.</p>
<p>The first entry of the vector table must be the stack pointer. The stack
-pointer must be initializes to the highest memory location available to
+pointer must be initialized to the highest memory location available to
accommodate the ATSAM3X8E’s descending stack.</p>
<p>The second entry of the vector table must be the reset vector. In the reset
diff --git a/_site/feed.xml b/_site/feed.xml
index 00889b5..850bb0c 100644
--- a/_site/feed.xml
+++ b/_site/feed.xml
@@ -1 +1 @@
-<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://localhost:4000/feed.xml" rel="self" type="application/atom+xml" /><link href="http://localhost:4000/" rel="alternate" type="text/html" /><updated>2025-12-15T22:35:43+08:00</updated><id>http://localhost:4000/feed.xml</id><title type="html">ASCIIMX | Blog</title><author><name>W. D. Sadeep Madurange</name></author><entry><title type="html">How I manage Suckless software installations</title><link href="http://localhost:4000/blog/suckless-software/" rel="alternate" type="text/html" title="How I manage Suckless software installations" /><published>2025-11-30T00:00:00+08:00</published><updated>2025-11-30T00:00:00+08:00</updated><id>http://localhost:4000/blog/suckless-software</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Since suckless software requires users to modify the source code and recompile to customize, I need a way to maintain patches over the long term while retaining the ability to upgrade the software as new versions are released.]]></summary></entry><entry><title type="html">Neo4J A* search</title><link href="http://localhost:4000/blog/neo4j-a-star-search/" rel="alternate" type="text/html" title="Neo4J A* search" /><published>2025-09-14T00:00:00+08:00</published><updated>2025-09-14T00:00:00+08:00</updated><id>http://localhost:4000/blog/neo4j-a-star-search</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Back in 2018, we used Neo4J graph database to track the movement of marine vessels. We were interested in the shortest path a ship could take through a network of about 13,000 route points. Algorithms based on graph theory, such as A* search, provide optimal solutions to such problems. In other words, the set of route points lends itself well to a model based on graphs.]]></summary></entry><entry><title type="html">MOSFETs as electronic switches</title><link href="http://localhost:4000/blog/mosfet-switches/" rel="alternate" type="text/html" title="MOSFETs as electronic switches" /><published>2025-06-22T00:00:00+08:00</published><updated>2025-06-22T00:00:00+08:00</updated><id>http://localhost:4000/blog/mosfet-switches</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Recently, I needed a low-power circuit for one of my battery-operated projects. Much of the system’s power savings depended on its ability to electronically switch off components, such as servos, that draw high levels of quiescent currents. My search for a solution led me to MOSFETs, transistors capable of controlling circuits operating at voltages far above their own.]]></summary></entry><entry><title type="html">How to configure ATmega328P microcontrollers to run at 3.3V and 5V</title><link href="http://localhost:4000/blog/arduino-uno/" rel="alternate" type="text/html" title="How to configure ATmega328P microcontrollers to run at 3.3V and 5V" /><published>2025-04-10T00:00:00+08:00</published><updated>2025-04-10T00:00:00+08:00</updated><id>http://localhost:4000/blog/arduino-uno</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This is a quick reference for wiring up ATmega328P ICs to run at 5V and 3.3V. While the 5V configuration is common, the 3.3V configuration can be useful in low-power applications and when interfacing with parts that themselves run at 3.3V. In this guide, the 5V setup is configured with a 16MHz crystal oscillator, while the 3.3V configuration makes use of an 8MHz crystal oscillator.]]></summary></entry><entry><title type="html">How to set up ATSAM3X8E microcontrollers for bare-metal programming in C</title><link href="http://localhost:4000/blog/arduino-due/" rel="alternate" type="text/html" title="How to set up ATSAM3X8E microcontrollers for bare-metal programming in C" /><published>2024-10-05T00:00:00+08:00</published><updated>2024-10-05T00:00:00+08:00</updated><id>http://localhost:4000/blog/arduino-due</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This article is a step-by-step guide for programming bare-metal ATSAM3X8E chips found on Arduino Due boards. It also includes notes on the chip’s memory layout relevant for writing linker scripts. The steps described in this article were tested on an OpenBSD workstation.]]></summary></entry></feed> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/feed.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-17T07:12:59+08:00</updated><id>/feed.xml</id><title type="html">ASCIIMX | Blog</title><author><name>W. D. Sadeep Madurange</name></author><entry><title type="html">How I manage Suckless software installations</title><link href="/blog/suckless-software/" rel="alternate" type="text/html" title="How I manage Suckless software installations" /><published>2025-11-30T00:00:00+08:00</published><updated>2025-11-30T00:00:00+08:00</updated><id>/blog/suckless-software</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Since suckless software requires users to modify the source code and recompile to customize, I need a way to maintain patches over the long term while retaining the ability to upgrade the software as new versions are released.]]></summary></entry><entry><title type="html">Neo4J A* search</title><link href="/blog/neo4j-a-star-search/" rel="alternate" type="text/html" title="Neo4J A* search" /><published>2025-09-14T00:00:00+08:00</published><updated>2025-09-14T00:00:00+08:00</updated><id>/blog/neo4j-a-star-search</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Back in 2018, we used Neo4J graph database to track the movement of marine vessels. We were interested in the shortest path a ship could take through a network of about 13,000 route points. Algorithms based on graph theory, such as A* search, provide optimal solutions to such problems. In other words, the set of route points lends itself well to a model based on graphs.]]></summary></entry><entry><title type="html">MOSFETs as electronic switches</title><link href="/blog/mosfet-switches/" rel="alternate" type="text/html" title="MOSFETs as electronic switches" /><published>2025-06-22T00:00:00+08:00</published><updated>2025-06-22T00:00:00+08:00</updated><id>/blog/mosfet-switches</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[Recently, I needed a low-power circuit for one of my battery-operated projects. Much of the system’s power savings depended on its ability to electronically switch off components, such as servos, that draw high levels of quiescent currents. My search for a solution led me to MOSFETs, transistors capable of controlling circuits operating at voltages far above their own.]]></summary></entry><entry><title type="html">How to configure ATmega328P microcontrollers to run at 3.3V and 5V</title><link href="/blog/arduino-uno/" rel="alternate" type="text/html" title="How to configure ATmega328P microcontrollers to run at 3.3V and 5V" /><published>2025-04-10T00:00:00+08:00</published><updated>2025-04-10T00:00:00+08:00</updated><id>/blog/arduino-uno</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This is a quick reference for wiring up ATmega328P ICs to run at 5V and 3.3V. While the 5V configuration is common, the 3.3V configuration can be useful in low-power applications and when interfacing with parts that themselves run at 3.3V. In this guide, the 5V setup is configured with a 16MHz crystal oscillator, while the 3.3V configuration makes use of an 8MHz crystal oscillator.]]></summary></entry><entry><title type="html">How to set up ATSAM3X8E microcontrollers for bare-metal programming in C</title><link href="/blog/arduino-due/" rel="alternate" type="text/html" title="How to set up ATSAM3X8E microcontrollers for bare-metal programming in C" /><published>2024-10-05T00:00:00+08:00</published><updated>2024-10-05T00:00:00+08:00</updated><id>/blog/arduino-due</id><author><name>W. D. Sadeep Madurange</name></author><summary type="html"><![CDATA[This article is a step-by-step guide for programming bare-metal ATSAM3X8E chips found on Arduino Due boards. It also includes notes on the chip’s memory layout relevant for writing linker scripts. The steps described in this article were tested on an OpenBSD workstation.]]></summary></entry></feed> \ No newline at end of file
diff --git a/_site/posts.xml b/_site/posts.xml
index 4a92130..7a71ed1 100644
--- a/_site/posts.xml
+++ b/_site/posts.xml
@@ -1 +1 @@
-<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="http://localhost:4000/posts.xml" rel="self" type="application/atom+xml" /><link href="http://localhost:4000/" rel="alternate" type="text/html" /><updated>2025-12-15T22:35:43+08:00</updated><id>http://localhost:4000/posts.xml</id><title type="html">ASCIIMX</title><author><name>W. D. Sadeep Madurange</name></author></feed> \ No newline at end of file
+<?xml version="1.0" encoding="utf-8"?><feed xmlns="http://www.w3.org/2005/Atom" ><generator uri="https://jekyllrb.com/" version="4.4.1">Jekyll</generator><link href="/posts.xml" rel="self" type="application/atom+xml" /><link href="/" rel="alternate" type="text/html" /><updated>2025-12-17T07:12:59+08:00</updated><id>/posts.xml</id><title type="html">ASCIIMX</title><author><name>W. D. Sadeep Madurange</name></author></feed> \ No newline at end of file
diff --git a/_site/projects/e-reader/index.html b/_site/projects/e-reader/index.html
index 2e04274..ea997c4 100644
--- a/_site/projects/e-reader/index.html
+++ b/_site/projects/e-reader/index.html
@@ -127,7 +127,7 @@ and RTC memory.</p>
features like an offline library of books and touch screens, there’s something
to be said about reading on hardware you built yourself. You are no longer the
powerless Muggle watching others perform magic. You are the wizard who makes
-the invisible particles swirl into form by whispering C to it.</p>
+the invisible particles swirl into form by whispering C to them.</p>
<p>Files: <a href="source.tar.gz">source.tar.gz</a></p>
</div>
diff --git a/_site/projects/etlas/index.html b/_site/projects/etlas/index.html
index 6b2906b..ae30cb6 100644
--- a/_site/projects/etlas/index.html
+++ b/_site/projects/etlas/index.html
@@ -44,7 +44,9 @@
<h2 class="center" id="title">ETLAS: E-PAPER DASHBOARD</h2>
<h6 class="center">05 SEPTEMBER 2024</h5>
<br>
- <div class="twocol justify"><p>Etlas is a weather, news, and stock price tracking system.</p>
+ <div class="twocol justify"><p>Etlas is a news, stock market, and weather tracker powered by an ESP32 NodeMCU
+D1, featuring a 7.5-inch <a href="https://www.waveshare.com/" class="external" target="_blank" rel="noopener noreferrer">Waveshare</a> e-paper display and a
+DHT22 sensor module.</p>
<table style="border: none;">
<tr style="border: none;">
@@ -53,80 +55,96 @@
</tr>
</table>
-<h2 id="acknowledgements">Acknowledgements</h2>
+<p>The top-left panel shows two weeks of end-of-day prices—the maximum the ESP32’s
+SRAM can hold—from the Polygon.io API. The price feed is relayed through a
+FastCGI-wrapped Flask app hosted on a VPS. This lets me configure stock symbols
+in its application settings. The app cycles through them as requests come in
+from the ESP32. Running the Flask app as a FastCGI process while exposing it
+via httpd with htpasswd authentication keeps the server code simple and secure.</p>
-<p>The e-paper display driver in the epd.c file of the project is derived from <a href="https://github.com/waveshareteam/e-Paper" class="external" target="_blank" rel="noopener noreferrer">Waveshare examples</a> for Arduino
-and STM32 platforms.</p>
-
-<p>The exceptionally elegant algorithm in the file dht.c, which reads DHT22 sensor
-data by comparing consecutive pulses, I ported directly from <a href="https://github.com/Fonger/ESP8266-RTOS-DHT" class="external" target="_blank" rel="noopener noreferrer">this</a> implementation for ESP8266
-modules to my ESP32. All credit for the algorithm belongs to them.</p>
-
-<h2 id="overview">Overview</h2>
-
-<p>Etlas comprises an embedded system featuring an ESP32 NodeMCU D1
-microcontroller, a 7.5″ <a href="https://www.waveshare.com/" class="external" target="_blank" rel="noopener noreferrer">Waveshare</a> e-paper display, a
-DHT22 weather sensor, and a server backend consisting of a FastCGI-wrapped
-Flask app. The following diagram outlines this system architecture.</p>
+<p>The following diagram outlines the Etlas’s overall system architecture.</p>
<p><img src="etlas_arch.png" alt="architecture" /></p>
-<p>The embedded application, written in C with the help of the ESP-IDF v5.2.1,
-connects to a (2.4GHz) WiFi network at startup and obtains time from an NTP
-server. It then composites and updates an internal pixel buffer using data
-acquired from various data sources and renders to the screen by writing to its
-Serial Peripheral Interface.</p>
-
-<h2 id="price-curves">Price curves</h2>
-
-<p>On the e-paper display, the top-left panel displays end-of-day price curves
-over two weeks from the <a href="https://polygon.io/" class="external" target="_blank" rel="noopener noreferrer">Polygon.io</a> API. The
-microcontroller’s 512KB SRAM limits the price curves to two weeks. Instead of
-using raster images for complex graphics—like e-paper projects often do—Etlas
-computes the price curves from CSV data on the fly using the ESP32’s 160MHz
-microprocessor. To avoid the overhead of floating-point arithmetic, all
-computations are performed on integers. The <code class="language-plaintext highlighter-rouge">gui_plot_stocks()</code> function in the
-gui.c file contains the rendering logic.</p>
-
-<p>Proxying the price feeds through the Flask app permits me to configure the
-tickers I’m interested in via its application settings. The Flask app cycles
-through the tickers in its configuration file as it receives requests from the
-embedded software. The Flask app is secured with basic authentication
-implemented using htpasswd and OpenBSD’s httpd web server. This FastCGI-wrapped
-architecture dramatically reduces the complexity of the server code and
-increases system reliability.</p>
-
-<h2 id="news-feed">News feed</h2>
-
-<p>The more prominent panel on the right displays news from <a href="https://www.channelnewsasia.com/" class="external" target="_blank" rel="noopener noreferrer">Channel News Asia</a>. The embedded program downloads
-and parses the RSS feed before rendering it to the display. The character
-glyphs used for rendering news are stored in header files in the sprites
-directory as bitmaps. I created the glyphs manually by saving them as images in
-GIMP and rasterizing them with ImageMagick.</p>
-
-<p>I connected the embedded system directly to the RSS feed to write less server
-code (the focus of this project was the embedded system). In hindsight,
-however, it limits the feeds from which Etlas can receive data. In a future
-version, I will relay the RSS feed through the backend (like the stock prices)
-to make it more flexible.</p>
-
-<h2 id="weather-data">Weather data</h2>
-
-<p>The bottom panels (middle and right) display the temperature and relative
-humidity from a DHT22 sensor. The DHT22 driver, arguably the most interesting
-part of the software, reads real-time sensor data by comparing relative pulse
-widths. The pulses themselves are too quick for the ESP32 to reliably measure
-directly.</p>
-
-<p>Much of the heavy lifting of acquiring, interpreting, and rendering data from
-different data sources is performed on the microcontroller using less than 512
-KB of memory. The embedded software that makes that possible is written in C
-using the ESP-IDF v5.2.1. My e-paper display driver is a port of Waveshare <a href="https://github.com/waveshareteam/e-Paper" class="external" target="_blank" rel="noopener noreferrer">examples</a> for Arduino
-and STM32 platforms.</p>
-
-<p>I’ve been using Etlas daily (for a couple of hours on weekdays and all day on
-weekends) since August 2024. As of October 2025, it’s been running reliably for
-over a year.</p>
+<p>The more prominent panel on the right of the display shows local and world news
+from Channel NewsAsia. The MCU downloads and parses XML data from the RSS feed
+directly before rendering it to the display. The character glyphs used are
+stored as bitmaps in the sprites directory. I skipped the proxy for news to
+avoid writing more server code, but in hindsight it limits the feeds Etlas can
+handle. I will fix this in a future version.</p>
+
+<p>The middle and bottom right panels display the temperature and relative
+humidity from the DHT22 sensor. The DHT22 uses pulse-width modulation to
+transmit data to the host. The 26µs, 50µs, and 70µs pulses are too fast for the
+ESP32 to measure reliably with standard APIs. Instead, the driver compares
+relative pulse widths to differentiate zeros from ones:</p>
+
+<div class="language-plaintext highlighter-rouge"><div class="highlight"><pre class="highlight"><code>static inline int dht_await_pin_state(int state, int timeout)
+{
+ int t;
+ static const uint16_t delta = 1;
+
+ for (t = 0; t &lt; timeout; t += delta) {
+ ets_delay_us(delta);
+ if (gpio_get_level(DHT_PIN) == state)
+ return t;
+ }
+ return 0;
+}
+
+static inline int dht_get_raw_data(unsigned char buf[DHT_DATA_LEN])
+{
+ int rc;
+ unsigned char i, pwl, pwh;
+
+ gpio_set_level(DHT_PIN, 0);
+ ets_delay_us(1100);
+ gpio_set_level(DHT_PIN, 1);
+
+ if (!dht_await_pin_state(0, 40)) {
+ rc = 1;
+ xQueueSend(dht_evt_queue, &amp;rc, (TickType_t) 0);
+ return 0;
+ }
+ if (!dht_await_pin_state(1, 80)) {
+ rc = 2;
+ xQueueSend(dht_evt_queue, &amp;rc, (TickType_t) 0);
+ return 0;
+ }
+ if (!dht_await_pin_state(0, 80)) {
+ rc = 3;
+ xQueueSend(dht_evt_queue, &amp;rc, (TickType_t) 0);
+ return 0;
+ }
+
+ for (i = 0; i &lt; DHT_DATA_LEN; i++) {
+ if (!(pwl = dht_await_pin_state(1, 50))) {
+ rc = 4;
+ xQueueSend(dht_evt_queue, &amp;rc, (TickType_t) 0);
+ return 0;
+ }
+ if (!(pwh = dht_await_pin_state(0, 70))) {
+ rc = 5;
+ xQueueSend(dht_evt_queue, &amp;rc, (TickType_t) 0);
+ return 0;
+ }
+ buf[i] = pwh &gt; pwl;
+ }
+ return 1;
+}
+</code></pre></div></div>
+
+<p>I ported <a href="https://github.com/Fonger/ESP8266-RTOS-DHT" class="external" target="_blank" rel="noopener noreferrer">this</a> implementation from ESP8266
+to ESP32—all credit for the algorithm belongs to them.</p>
+
+<p>Etlas is a networked embedded system. All acquisition, processing, and
+rendering of data are performed on the ESP32’s 160MHz microprocessor using less
+than 512KB of SRAM. The embedded software that makes this possible is written
+in C using ESP-IDF v5.2.1. The e-paper display driver is derived from Waveshare
+<a href="https://github.com/waveshareteam/e-Paper" class="external" target="_blank" rel="noopener noreferrer">examples</a> for Arduino and STM32
+platforms.</p>
+
+<p>Etlas has been running reliably for over a year since August 2024.</p>
<p>Files: <a href="source.tar.gz">source.tar.gz</a></p>
</div>
diff --git a/_site/projects/matrix-digital-rain/index.html b/_site/projects/matrix-digital-rain/index.html
index 2893c92..b2107b4 100644
--- a/_site/projects/matrix-digital-rain/index.html
+++ b/_site/projects/matrix-digital-rain/index.html
@@ -45,7 +45,7 @@
<h6 class="center">12 JANUARY 2024</h5>
<br>
<div class="twocol justify"><p>“All I see is blonde, brunette, red head.” The iconic digital rain from The
-Matrix in C, with zero dependencies - not even ncurses.</p>
+Matrix in C, with zero dependencies—not even ncurses.</p>
<video style="max-width:100%;" controls="" poster="thumb.png">
<source src="matrix.mp4" type="video/mp4" />
diff --git a/_site/robots.txt b/_site/robots.txt
index d297064..e087884 100644
--- a/_site/robots.txt
+++ b/_site/robots.txt
@@ -1 +1 @@
-Sitemap: http://localhost:4000/sitemap.xml
+Sitemap: /sitemap.xml
diff --git a/_site/sitemap.xml b/_site/sitemap.xml
index 5130d9f..1b23cd3 100644
--- a/_site/sitemap.xml
+++ b/_site/sitemap.xml
@@ -1,59 +1,59 @@
<?xml version="1.0" encoding="UTF-8"?>
<urlset xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.sitemaps.org/schemas/sitemap/0.9 http://www.sitemaps.org/schemas/sitemap/0.9/sitemap.xsd" xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">
<url>
-<loc>http://localhost:4000/blog/arduino-due/</loc>
+<loc>/blog/arduino-due/</loc>
<lastmod>2024-10-05T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/blog/arduino-uno/</loc>
+<loc>/blog/arduino-uno/</loc>
<lastmod>2025-04-10T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/blog/mosfet-switches/</loc>
+<loc>/blog/mosfet-switches/</loc>
<lastmod>2025-06-22T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/blog/neo4j-a-star-search/</loc>
+<loc>/blog/neo4j-a-star-search/</loc>
<lastmod>2025-09-14T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/blog/suckless-software/</loc>
+<loc>/blog/suckless-software/</loc>
<lastmod>2025-11-30T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/projects/e-reader/</loc>
+<loc>/projects/e-reader/</loc>
<lastmod>2023-10-24T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/projects/matrix-digital-rain/</loc>
+<loc>/projects/matrix-digital-rain/</loc>
<lastmod>2024-01-12T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/projects/etlas/</loc>
+<loc>/projects/etlas/</loc>
<lastmod>2024-09-05T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/projects/bumblebee/</loc>
+<loc>/projects/bumblebee/</loc>
<lastmod>2025-04-02T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/projects/my-first-pcb/</loc>
+<loc>/projects/my-first-pcb/</loc>
<lastmod>2025-07-14T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/projects/fpm-door-lock/</loc>
+<loc>/projects/fpm-door-lock/</loc>
<lastmod>2025-10-03T00:00:00+08:00</lastmod>
</url>
<url>
-<loc>http://localhost:4000/about/</loc>
+<loc>/about/</loc>
</url>
<url>
-<loc>http://localhost:4000/blog/</loc>
+<loc>/blog/</loc>
</url>
<url>
-<loc>http://localhost:4000/</loc>
+<loc>/</loc>
</url>
<url>
-<loc>http://localhost:4000/projects/</loc>
+<loc>/projects/</loc>
</url>
</urlset>