These are the pages I kept open: the Arduino Nano, Arduino Mega 2560, the ESP8266 Arduino core, and EspSoftwareSerial. The DHT reads are the Adafruit DHT library. The name environment.lan comes from dnsmasq, which is the DNS server on the AX3200, documented under DHCP and DNS page.
I bought a pile of sensor modules in 2019 and most of them sat in a drawer, so I put as many as I could on one system. That is two DHT11 temperature and humidity sensors, an LDR light module, an MQ smoke module, an MQ alcohol module, a black infrared flame sensor, an HC-05 Bluetooth module, an HC-06 Bluetooth module, and a 4-pin SSD1306 OLED display. I also wanted a soil sensor, a rain sensor, and a TCS230 colour sensor on it. Those three are still in the drawer. There is no cloud account. The page is served on the local network.
I made it because I wanted something like this in the room. I barely look at the OLED, and I do not open the page every day. I live in Colombo. Outside gets very hot, so I use the air conditioner, but the electricity bill is high enough that some days I switch it off and use a fan. The outdoor temperature is the number that would tell me when to do that. I have not built a beep for it yet.
I used three boards because I already owned them and wanted to use them. The Arduino Nano goes through a small hole in the window, with long wires to the DHT11, and the HC-06 sits out there with it. The Arduino Mega 2560 stays indoors because that is where the analog sensors are. The NodeMCU LoLin V3, which is an ESP-12E, serves the page. I left its main serial port, UART0, on the USB cable so I can still program it.
Outdoor Nano
The Arduino Nano reads the outdoor DHT11 on pin D2 every five seconds and sends one line to the HC-06. The HC-06 is the Bluetooth slave. I used SoftwareSerial because the USB serial port was already taken: D10 is RX, D11 is TX, 9600 baud. The line looks like this:
OUT_TEMP:24.0,OUT_HUM:84.0If the DHT11 read fails, the Arduino Nano keeps the last good sample and does not send a line until it has a real reading. The Arduino Nano has its own USB supply. I did not power it from the Arduino Mega 2560 5 V pin.
Indoor Mega
I set the HC-05 to master in AT mode before this sketch. The sketch doesn't send AT commands. It just talks to that HC-05 on Serial1. HC-05 TX goes to D19. Mega D18 goes through a 1 kΩ / 2 kΩ divider into HC-05 RX. 9600. It reads:
- DHT11 on D22, indoor temperature and humidity
- LDR light module on A0. I scale the analog reading to a percentage from 0 to 100. It is not lux, because I never measured it against a light meter.
- MQ smoke module on A1, raw 0-1023. It also reacts to other gases.
- MQ alcohol module on A2, also a raw 0-1023 gas reading. I never calibrated it, so it is not a ppm meter.
- The flame module's digital pin, D24, goes low when it sees infrared. I wait for 2 of 3 samples before I call it a flame. The analog pin is A3. My air conditioner remote tripped it, so a remote is enough.
If no HC-06 line arrives for 20 seconds, the Mega sets OUT_OK to 0. The page replaces outdoor temperature and humidity with -- and says the outdoor sensor is offline. The OLED outdoor page says OFFLINE. Indoor numbers stay. Every 3 seconds Serial2 (TX2 D16, RX2 D17, 9600) sends one packet to the NodeMCU. CHK is the ASCII sum before ,CHK:, mod 10000. The checksum only told me the line was bad. The cause was the voltage divider on D16.
There's a 128×64 SSD1306 on I2C, address 0x3C, SDA D20, SCL D21. It flips through four pages every 4 seconds: indoor, outdoor, smoke / alcohol / flame, then light. If I unplug the OLED, the serial packet still goes out.
The NodeMCU page
UART0 stays on the USB port so I can still program it and read logs. The sketch prints at 115200. The Mega link is SoftwareSerial on D5 (GPIO14, RX) and D6 (GPIO12, TX), 9600. I passed 256 to begin() because the default 64-byte buffer was too small for the Mega line. The NodeMCU joins the 2.4 GHz SSID, serves / and /api/data, and calls the Mega link stale after 12 seconds with no good packet.
The page is plain HTML and a fetch loop. No framework on the board, no Google Fonts, no CDN. I open it from phones on this LAN.


The router only does the name
The NodeMCU is the one actually serving the page. The Wi-Fi is a Xiaomi AX3200. I flashed OpenWrt on it last year for the house, and that's why this thing has a hostname at all.
The NodeMCU asks for the hostname environment. If the phone is using the router's DNS, that's http://environment.lan. I just type .lan. If the phone's DNS skips the router, the name misses and I use the reserved IP. The board also announces environment.local.
configTime asks the gateway for the clock. OpenWrt won't answer unless NTP server mode is on. If it isn't, the page still tells me how many seconds ago the packet came in, so I don't really need the clock for this.
I already had an Onion Omega from 2018. It runs its own OpenWrt, it has a filesystem and a web server, and out of the box it just joins a LAN as a client. This page is one HTML file and one JSON route. The AX3200 already does DHCP and DNS. I didn't need another Linux box sitting there.
Wiring that actually mattered
- Mega TX2 (D16) is 5 V and NodeMCU D5 is 3.3 V, so you can't just wire them. 1 kΩ from D16 to the midpoint, 2 kΩ from the midpoint to ground, midpoint to D5. Same divider on HC-05 RX and HC-06 RX.
- Mega GND has to be tied to NodeMCU GND or the divider does nothing. A 5 V 2 A supply feeds the Mega 5 V rail, and the MQ heaters draw from that pin. The NodeMCU stays on its own USB cable.
- HC-06 silence timeout is 20 seconds. The page calls the Mega link stale at 12 seconds. Those are two different timers.
The gas sensors are not calibrated
Both MQ modules are just raw 0-1023 numbers. I mark Elevated at 400 and High at 700 on this page. That's just where I drew the lines. Flame comes from this module's digital pin.
I'm thinking of upgrading this to a BME680. That one does pressure, temperature, humidity, and gas.