Wi-Fi In the Woods
People use smart phones for almost everything today. Why not use one to solve a puzzle?
I put this together in early 2021 as the second thing I’d ever built with an ESP8266. I’ve done some Arduino development before but I’m learning the quirks of the ESP8266. This puzzle box is a plastic ammo can (to prevent blocking the WI-Fi signals) with a smaller locking area inside for the log. At first it looks like a normal geocache.
But the other side gives a better hint. The Cricut machine makes labels like this easy to create.
Opening the box, you can see the puzzle. The copper colored door is a lockable outlet cover. This is used to secure the log and swag items. A small 4 digit padlock keeps the cover closed. (The lock is not shown in these photos.)
The puzzle is powered by 4xAA batteries supplied by the finder. Below that is a translucent blue 3D printed strip with the numbers 0 through 9. The text on the label provides the SSID, Wi-Fi password, and web address needed to access the puzzle.
Puzzle_Box
PW: 12345678
192.168.1.1
When the batteries are in place, the cache circuit activates and shows the blue power LED.
The NodeMCU used in the puzzle generates its own Wi-Fi network called Puzzle_Box. This is the W-Fi setup screen from a cell phone.
Connecting a smart device and opening the web page found at 192.168.1.1 reveals the puzzle control screen.
Selecting each button toggles it between the gray SHOW button and a green HIDE button. It also toggles the matching combination digit LED on and off. This lights green LEDs below numbers on the blue strip to show the digits of the lock code. Here the first digit of the code is selected and the LED for the number 7 is illuminated. The first digit of the code is 7. Selecting the second, third, and fourth buttons provides the rest of the code. Not a complex puzzle, but something out of the ordinary.
The electrical box and cover is a simple way to create a lockable space inside a geocache container. This puzzle as constructed is more suited for events and indoor spaces. With a few changes to make it more weatherproof it could be placed in the field. You can find a video of the cache in operation on my YouTube channel MakerCaches.
The heart of the build is the NodeMCU operating as a Wi-Fi access point. I like using the NodeMCU because it is cheap (under $5 online) and it uses the Arduino development environment. It does act differently from an Arduino in some ways. It is extremely important to remember that this module uses 3.3V and not 5V for power and I/O. The NodeMCU also has some I/O pins that briefly change state during boot, making the LEDs used in the puzzle blink for a moment when batteries are inserted. I added some extra LEDs behind other numbers so that the initial blink won’t reveal the lock code.
The circuit uses the NodeMCU’s onboard voltage regulator to supply the 3.3V needed. The 4 AA batteries are connected to the Vin pin through a diode to prevent reverse power connections that might damage the module. Beyond that the only other parts are LEDs of your choice and matching current limiting resistors. The forward voltage of LEDs varies by color and type, so the resistors are selected to match the LEDs used.
I found an excellent article online explaining the creation of a captive portal using the ESP8266 and used it as the starting point of this project. I modified the code to suit the puzzle and added the 3rd and 4th digits. Substack doesn’t have the best formatting for software code. Send me a note if you would like it as a .txt file that is easier to read.
#include <ESP8266WiFi.h>
#include <ESP8266WebServer.h>
//https://lastminuteengineers.com/creating-esp8266-web-server-arduino-ide/
//Modified to have four buttons and outputs 3-31-21 ELM
//V2 - Added status LED to show uP is up and running 4-6-21 ELM
/* Put your SSID & Password */
const char* ssid = "Puzzle_Box"; // Enter SSID here
const char* password = "12345678"; //Enter Password here
/* Put IP Address details */
IPAddress local_ip(192,168,1,1);
IPAddress gateway(192,168,1,1);
IPAddress subnet(255,255,255,0);
ESP8266WebServer server(80);
uint8_t LED1pin = D7;
bool LED1status = LOW;
uint8_t LED2pin = D6;
bool LED2status = LOW;
uint8_t LED3pin = D5;
bool LED3status = LOW;
uint8_t LED4pin = D3; //D4 conflicts with onboard blue led
bool LED4status = LOW;
uint8_t LEDPWRpin = D2; //power LED
bool LEDPWRstatus = LOW;
uint8_t LED8pin = D8; //unused output
bool LED8status = LOW;
uint8_t LED5pin = D1; //unused output
bool LED5status = LOW;
uint8_t LED0pin = D0; //unused output
bool LED0status = LOW;
void setup() {
Serial.begin(115200);
pinMode(LED1pin, OUTPUT);
pinMode(LED2pin, OUTPUT);
pinMode(LED3pin, OUTPUT);
pinMode(LED4pin, OUTPUT);
pinMode(LEDPWRpin, OUTPUT);
pinMode(LED8pin, OUTPUT);
pinMode(LED5pin, OUTPUT);
pinMode(LED0pin, OUTPUT);
WiFi.softAP(ssid, password);
WiFi.softAPConfig(local_ip, gateway, subnet);
delay(100);
server.on("/", handle_OnConnect);
server.on("/led1on", handle_led1on);
server.on("/led1off", handle_led1off);
server.on("/led2on", handle_led2on);
server.on("/led2off", handle_led2off);
server.on("/led3on", handle_led3on);
server.on("/led3off", handle_led3off);
server.on("/led4on", handle_led4on);
server.on("/led4off", handle_led4off);
server.onNotFound(handle_NotFound);
server.begin();
Serial.println(" ");
Serial.println("HTTP server started");
}
void loop() {
digitalWrite(LEDPWRpin, HIGH);
server.handleClient();
if(LED1status)
{digitalWrite(LED1pin, HIGH);}
else
{digitalWrite(LED1pin, LOW);}
if(LED2status)
{digitalWrite(LED2pin, HIGH);}
else
{digitalWrite(LED2pin, LOW);}
if(LED3status)
{digitalWrite(LED3pin, HIGH);}
else
{digitalWrite(LED3pin, LOW);}
if(LED4status)
{digitalWrite(LED4pin, HIGH);}
else
{digitalWrite(LED4pin, LOW);}
}
void handle_OnConnect() {
LED1status = LOW;
LED2status = LOW;
LED3status = LOW;
LED4status = LOW;
Serial.println("GPIO7 Status: OFF | GPIO6 Status: OFF | GPIO5 Status: OFF | GPIO3 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,LED2status,LED3status,LED4status));
}
void handle_led1on() {
LED1status = HIGH;
Serial.println("GPIO7 Status: ON");
server.send(200, "text/html", SendHTML(true,LED2status,LED3status,LED4status));
}
void handle_led1off() {
LED1status = LOW;
Serial.println("GPIO7 Status: OFF");
server.send(200, "text/html", SendHTML(false,LED2status,LED3status,LED4status));
}
void handle_led2on() {
LED2status = HIGH;
Serial.println("GPIO6 Status: ON");
server.send(200, "text/html", SendHTML(LED1status,true,LED3status,LED4status));
}
void handle_led2off() {
LED2status = LOW;
Serial.println("GPIO6 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,false,LED3status,LED4status));
}
void handle_led3on() {
LED3status = HIGH;
Serial.println("GPIO5 Status: ON");
server.send(200, "text/html", SendHTML(LED1status,LED2status,true,LED4status));
}
void handle_led3off() {
LED3status = LOW;
Serial.println("GPIO5 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,LED2status,false,LED4status));
}
void handle_led4on() {
LED4status = HIGH;
Serial.println("GPIO3 Status: ON");
server.send(200, "text/html", SendHTML(LED1status,LED2status,LED3status,true));
}
void handle_led4off() {
LED4status = LOW;
Serial.println("GPIO3 Status: OFF");
server.send(200, "text/html", SendHTML(LED1status,LED2status,LED3status,false));
}
void handle_NotFound(){
server.send(404, "text/plain", "Not found - Use the buttons, don't enter other links");
}
String SendHTML(uint8_t led1stat,uint8_t led2stat,uint8_t led3stat,uint8_t led4stat){
String ptr = "<!DOCTYPE html> <html>\n";
ptr +="<head><meta name=\"viewport\" content=\"width=device-width, initial-scale=1.0, user-scalable=no\">\n";
ptr +="<title>Geocache Control Screen</title>\n";
ptr +="<style>html { font-family: Helvetica; display: inline-block; margin: 0px auto; text-align: center;}\n";
ptr +="body{margin-top: 10px;} h1 {color: #444444;margin: 50px auto 30px;} h3 {color: #444444;margin-bottom: 10px;}\n";
ptr +=".button {display: block;width: 80px;background-color: #1abc9c;border: none;color: white;padding: 13px 30px;text-decoration: none;font-size: 25px;margin: 0px auto 35px;cursor: pointer;border-radius: 4px;}\n";
ptr +=".button-on {background-color: #b8b6b6;}\n";
ptr +=".button-on:active {background-color: #16a085;}\n";
ptr +=".button-off {background-color: #35ff03;}\n";
ptr +=".button-off:active {background-color: #2c3e50;}\n";
ptr +="p {font-size: 14px;color: #888;margin-bottom: 10px;}\n";
ptr +="</style>\n";
ptr +="</head>\n";
ptr +="<body>\n";
ptr +="<h3>Geocache Web Server</h3>\n";
ptr +="<h3>Use the buttons to reveal the combination digits</h3>\n";
if(led1stat)
{ptr +="<p>First Digit: Revealed</p><a class=\"button button-off\" href=\"/led1off\">HIDE</a>\n";}
else
{ptr +="<p>First Digit: Hidden</p><a class=\"button button-on\" href=\"/led1on\">SHOW</a>\n";}
if(led2stat)
{ptr +="<p>Second Digit: Revealed</p><a class=\"button button-off\" href=\"/led2off\">HIDE</a>\n";}
else
{ptr +="<p>Second Digit: Hidden</p><a class=\"button button-on\" href=\"/led2on\">SHOW</a>\n";}
if(led3stat)
{ptr +="<p>Third Digit: Revealed</p><a class=\"button button-off\" href=\"/led3off\">HIDE</a>\n";}
else
{ptr +="<p>Third Digit: Hidden</p><a class=\"button button-on\" href=\"/led3on\">SHOW</a>\n";}
if(led4stat)
{ptr +="<p>Fourth Digit: Revealed</p><a class=\"button button-off\" href=\"/led4off\">HIDE</a>\n";}
else
{ptr +="<p>Fourth Digit: Hidden</p><a class=\"button button-on\" href=\"/led4on\">SHOW</a>\n";}
ptr +="</body>\n";
ptr +="</html>\n";
return ptr;
}