I frequently check the weather on my phone, often finding it hasn’t changed. As there are already many screens around the house and even in our pockets or on our wrists that can display the weather forecast, I decided to create a display that is not just a screen, but a unique way of showing the weather forecast anywhere in the house.
It uses a Raspberry Pi Pico W with OpenWeatherMap API to get the latest weather forecast every 10 minutes and turn a stepper motor to show the right weather symbol.
All code & 3D models can be downloaded here.
Parts
- 3D Printed Enclosure
- Raspberry Pi Pico WH
- Solderless breadboard with 300 tie points
- MB‐V2 Breadboard Power Supply
- 28BYJ-48 5V Stepper Motor
- 4 Phase ULN2003 Stepper Motor Driver
- Jumper wires
Instructions
- 3D print the printed parts in a non-flexible material of your choice (I used PLA). You can find all the STL files on my printables page here. For 3d printing settings, I used the default for PLA on my Ender 3 S1 Pro. You may need supports for the hole in the bottom piece of the enclosure to make sure it prints well. I used a tree support with the ‘only on build plate’ setting turned on and it worked well.
- Place the breadboard in the 3d printed case and place the power supply on it. Make sure that for the side closest to the DC jack, the 3.3v pins are connected with the jumper, and on the opposite side that the 5v pins are connected with the jumper.
- Upload the code above to the Pico using the arduino IDE (make sure that you have the required libraries installed which are listed at the top of the code as include lines).
- Place the Pico in the remaining space on the breadboard.
- Connect the stepper motor to the stepper motor driver board with the cable attached to the stepper motor. I decided to use a stepper motor as it can be almost as precise as a servo and it can move in precise angles, but I also chose it as it was what I had on hand at the time and I did not have any 360-degree servos.
- Use the jumper wires to make the necessary connections between the power board, the Pico, and the motor driver according to the wiring diagram.
- Assemble the 3D-printed shaft and the weather icons in the order shown in the picture.
- Place the stepper motor inside the top part of the enclosure and place the top enclosure on the bottom one to complete the box.
- Place the shaft with the icons onto the stepper motor and use some blu tack to secure the shaft (using the blu tack is optional).
- Turn the shaft until the sun is at the front.
- Using a DC power supply cable, plug the breadboard power supply into power from an outlet with the hole in the enclosure and watch the weather display spin to life! (if it is sunny at the moment then the shaft will not spin because it is already a correct reading)
Code & Wiring Diagram
#include <WiFi.h> #include <HTTPClient.h> #include <Arduino_JSON.h> #include <Stepper.h> #include <string.h> const char* ssid = "Enter wifi SSID"; const char* password = "Enter wifi password"; const int stepsPerRevolution = 2048; const int rolePerMinute = 15; // Adjustable range of 28BYJ-48 stepper is 0~17 rpm const char* stepperPosition = "Sun"; // the current position of the stepper motor display in a string format (Sun, Cloud, Rain) Stepper myStepper(stepsPerRevolution, 12, 14, 13, 15); // Your Domain name with URL path or IP address with path String openWeatherMapApiKey = "enter openweathermap api key"; // Example: //String openWeatherMapApiKey = "bd939aa3d23ff33d3c8f5dd1dd435"; // Replace with your country code and city String city = "Insert location"; String countryCode = "country code"; // THE DEFAULT TIMER IS SET TO 10 SECONDS FOR TESTING PURPOSES // For a final application, check the API call limits per hour/minute to avoid getting blocked/banned unsigned long lastTime = 0; // Timer set to 10 minutes (600000) unsigned long timerDelay = 600000; // Set timer to 10 seconds (10000) //unsigned long timerDelay = 10000; String jsonBuffer; void updateStepperPosition(JSONVar parsedObject) { const char* weatherMain = parsedObject["weather"][0]["main"]; Serial.print("The weather condition is: "); Serial.println(weatherMain); //These are all of the if statements and logic for moving the stepper motor. if (strcmp(weatherMain, "Clear") == 0 || strcmp(weatherMain, "Hot") == 0) { if (strcmp(stepperPosition, "Sun") == 0) { stepperPosition = "Sun"; Serial.println(stepperPosition); } else if (strcmp(stepperPosition, "Cloud") == 0) { myStepper.step(-(stepsPerRevolution/3)); stepperPosition = "Sun"; Serial.println(stepperPosition); } else if (strcmp(stepperPosition, "Rain") == 0) { myStepper.step(stepsPerRevolution/3); stepperPosition = "Sun"; Serial.println(stepperPosition); } } else if (strcmp(weatherMain, "Clouds") == 0 || strcmp(weatherMain, "Mist") == 0 || strcmp(weatherMain, "Haze") == 0 || strcmp(weatherMain, "Smoke") == 0 || strcmp(weatherMain, "Dust") == 0) { if (strcmp(stepperPosition, "Sun") == 0) { myStepper.step(stepsPerRevolution/3); stepperPosition = "Cloud"; Serial.println(stepperPosition); } else if (strcmp(stepperPosition, "Cloud") == 0) { stepperPosition = "Cloud"; Serial.println(stepperPosition); } else if (strcmp(stepperPosition, "Rain") == 0) { myStepper.step(-(stepsPerRevolution/3)); stepperPosition = "Cloud"; Serial.println(stepperPosition); } } else if (strcmp(weatherMain, "Light Rain") == 0 || strcmp(weatherMain, "Heavy Rain") == 0 || strcmp(weatherMain, "Rain") == 0) { if (strcmp(stepperPosition, "Sun") == 0) { myStepper.step(-(stepsPerRevolution/3)); stepperPosition = "Rain"; Serial.println(stepperPosition); } else if (strcmp(stepperPosition, "Cloud") == 0) { myStepper.step(stepsPerRevolution/3); stepperPosition = "Rain"; Serial.println(stepperPosition); } else if (strcmp(stepperPosition, "Rain") == 0) { stepperPosition = "Rain"; Serial.println(stepperPosition); } } } void getWeatherData() { if (WiFi.status() == WL_CONNECTED) { String serverPath = "http://api.openweathermap.org/data/2.5/weather?q=" + city + "," + countryCode + "&APPID=" + openWeatherMapApiKey; //Serial.println(serverPath); jsonBuffer = httpGETRequest(serverPath.c_str()); //Serial.println(jsonBuffer); JSONVar myObject = JSON.parse(jsonBuffer); // JSON.typeof(jsonVar) can be used to get the type of the var if (JSON.typeof(myObject) == "undefined") { Serial.println("Parsing input failed!"); return; } //Serial.print("JSON object = "); //Serial.println(myObject); Serial.print("Weather Condition: "); Serial.println(myObject["weather"][0]["main"]); updateStepperPosition(myObject); } else { Serial.println("WiFi Disconnected"); } } void setup() { myStepper.setSpeed(rolePerMinute); Serial.begin(9600); WiFi.begin(ssid, password); Serial.println(""); Serial.println("Connecting"); while (WiFi.status() != WL_CONNECTED) { delay(250); Serial.print("."); } Serial.print("Connected to WiFi network with IP Address: "); Serial.println(WiFi.localIP()); getWeatherData(); } void loop() { if ((millis() - lastTime) > timerDelay) { getWeatherData(); lastTime = millis(); } } String httpGETRequest(const char* serverName) { WiFiClient client; HTTPClient http; // Your Domain name with URL path or IP address with path http.begin(client, serverName); // Send HTTP POST request int httpResponseCode = http.GET(); String payload = "{}"; if (httpResponseCode > 0) { //Serial.print("HTTP Response code: "); //Serial.println(httpResponseCode); payload = http.getString(); } else { Serial.print("Error code: "); Serial.println(httpResponseCode); } // Free resources http.end(); return payload; }