📜 ⬆️ ⬇️

Lamp showing the weather forecast

Many of us, before leaving the house in the morning, check the weather forecast for the day ahead. I always used my smartphone for this and, one day, I thought, why not make this process more simple and convenient. So, I came up with the idea of ​​creating a room lamp that would be able to show the weather forecast in my area, as well as warn about possible precipitation and wind speed.



Under the cut video and images showing the work of this lamp and detailed instructions for creating it.

Demonstration of work


The lamp can show the weather forecast for 14 hours ahead. Technically, inside the lamp there are 14 horizontal levels (RGB LED strips of 20 LEDs each). The first level below is the weather that will be at the beginning of the next hour. Each next level is plus 1 hour. Horizontal movement at each level is the wind speed. There is also a rain effect - parts flashing smoothly with all colors at the beginning and end of each level.
')
Here it should be warned that all the photos and videos in this article were taken by long torment, since the lamp shines very much, and I am not a very experienced photographer and my DSLR just did not want to shoot how it really is. I honestly tried so many times but did not achieve the same picture as in reality.



You can also watch a video of the lamp operation without a cover and a video demonstrating the display of precipitation (the edges of the stripes glowing in all colors).

Requirements and design


To begin with, let's try to formulate the requirements:


After some thought, I decided to stop at a rectangular lamp in which there will be 12 horizontal levels with 20 LEDs in each. This will allow us to display the weather forecast 12 hours ahead. The color of each level depends on the air temperature at that time. In this case, each level will have a sufficient number of LEDs to display various effects, such as rain, wind or thunder. Already then the number of levels was increased to 14, as there were extra LEDs.

Iron


First of all, it is necessary to determine the platform: a real-time microcontroller such as an Arduino or a full-fledged computer, such as the Raspberry Pi with an operating system on board. Each of these options has its pros and cons. At first glance, the Arduino is ideal for this project, if only because we don’t have to wait 10 seconds for the OS to load on the Raspberry Pi. But even if you use Arduino, an instant cold start will not work - there will still be a delay in the initialization of the wifi shield and the weather request on the server. I was also a little confused by the question about the simultaneous operation of wifi shild (at the time of requesting a forecast to the server) and the operation of the LED strip.

In turn, if you use the Raspberry Pi, we have only one drawback - load time.

It was decided to use the Raspberry Pi with the WiFi USB dongle EdiMax. This dongle was used by me on other projects and proved itself quite well.

Next you need to find the appropriate light sources. In rough estimate, the minimum we need is about 240 LEDs (12 levels of 20 LEDs each). The option in which they all have to be soldered one by one has not even been considered. Our choice is not big: either LED panels or LED strip. The panels are perfect for those who want a lamp that is not larger in size without different curvatures on the surface. I stopped at the LED tape, because I wanted to make a lamp of medium size.

Thus, an RGB LED strip of 2 meters with a pixel density of 144 pieces per meter was ordered. This tape has address LEDs (digitally-addressable type of LED strip), which means that we can form a signal in such a way that each LED will receive its data and display the color it should. For this is responsible WS2811 chip, which is located in each LED on the tape. Since there are 288 LEDs in total in the ribbon, it was decided to use them to the maximum and make 14 levels with 20 LEDs)

It should be noted that the Raspberry Pi delivers only 3.3 volts on its GPIO ports, but for the tape we need a control signal of 5 volts. Thus, we need a voltage converter (level converter chip). I used 74AHCT125.

Connection diagram (was taken from Adafruit Tutorial ):



In the nearest shop, a donor lamp with dimensions of 60 by 20 cm was looked after and purchased. The lamp was bought with the expectation that we would need to place a power supply unit for the LED strip there. Since we got 280 RGB + Raspberry Pi LEDs, a 5 volt 10 amp unit was ordered in a fairly compact package.

It was the turn to place all this in the lamp. With the power supply, the Raspberry Pi, everything was clear. What can not be said about how to fix the 14 segments with LEDs, which previously had to be soldered to each other. The LEDs would have to be at a certain distance from the matte cover, otherwise they would be visible and the light would be too harsh.

The original idea was to use aluminum strips and already stick pieces of tape to them. But having made one frame, I quickly realized that it would take too much time. After that I decided to print frames on a 3D printer. If you have access to a laser engraver, you will make it even faster. In the extreme case, you can do everything with your hands - cut out of wood or cardboard (after it is repeatedly gluing the layers).

Frame printing:



The first tests of the tape on the framework:



So we got all the necessary components and it was the turn of the assembly. The LED strip was cut into pieces of 20 LEDs. The pieces were soldered to each other and glued to the frames. Frames, in turn, were glued to the body. The power supply, all wiring and Raspberry Pi were placed in the space between the frames and the case.

Lamp assembly process:



Result ( In higher resolution ):



Service for weather forecasting


The lamp requires a service to get a weather forecast. There is a large number of free and shareware services for this. For example openweathermap.org or forecast.io . All of them have their limitations or certain specific features.

One of my main criteria was the ability to get an hourly weather forecast for the next 12+ hours. Unfortunately, openweathermap can only return the weather forecast for 3 hours in free mode. I also did not like the speed of this service, although it was not at all critical given that we are going to update the weather forecast no more than once every half hour.

At the same time, the not quite free forecast. I was pleased with the speed and detail of the data. He allowed to receive in one request all the data that I needed (temperature, wind speed and precipitation) for 12 hours ahead and even more. The number of requests that you can make is limited to 1000 per day in a free mode, but this is well within my requirements. In the end, I chose this resource, to be honest, just relying on my intuition.

We had to decide how we would receive the data: through an intermediate resource or directly from forecast.io. The JSON file that returned the forecast.io service weighed about 40 kilobytes for my location, which seemed redundant to me. I needed only 3 values ​​for each of 12 hours. In the end, I decided to create my own small service for 2 reasons - to minimize the amount of data sent to the lamp and ensure future extensibility if I have to change the source of data in the future or change the provider. Considering the fact that we need only 3 values ​​(temperature, wind speed and amount of precipitation) for each hour, we need to transmit 168 bytes (14 * 3 * int = 4). Also, my service will allow you to set the coordinates of the terrain and the values ​​of the minimum and maximum temperatures for a given terrain, to avoid storing this information on the Raspberry Pi side.

I wrote a Java servlet for working with forecast.io, which can cache values ​​between requests and returns requests from the cache in case of too frequent requests (in order not to exceed the limit of 1000 free requests per day). We only request a new forecast once every 5 minutes. The coordinates of the location as well as the API key for forecast.io the servlet takes from the system properties, so if we need to change the area, we can do it from outside the web application.

Servlet code
public class ForecastServlet extends HttpServlet { private static final String API_KEY = System.getenv("AL_API_KEY"); private static final int REQUEST_PERIOD = 5 * 60 * 1000; private static final int START_HOUR = 0; private static final int END_HOUR = 14; private static final int DATA_SIZE = 3 * 4 * (END_HOUR - START_HOUR); private static final int TEMP_MULTIPLY = 100; private static final int WIND_MULTIPLY = 100; private static final int PRECIP_MULTIPLY = 1000; private final String mutex = ""; private final ByteArrayOutputStream data = new ByteArrayOutputStream(DATA_SIZE); private long lastRequestTime; @Override protected void doGet(HttpServletRequest req, HttpServletResponse response) throws ServletException, IOException { synchronized (mutex) { if ((System.currentTimeMillis() - lastRequestTime) > REQUEST_PERIOD) { try { updateForecast(); } catch (IOException e) { e.printStackTrace(); } } response.setHeader("Content-Type", "application/octet-stream"); response.setHeader("Content-Length", "" + data.size()); response.getOutputStream().write(data.toByteArray()); response.getOutputStream().flush(); response.getOutputStream().close(); lastRequestTime = System.currentTimeMillis(); } } private void updateForecast() throws IOException { int maxTemp = Integer.valueOf(System.getenv("AL_MAX_TEMP")) * TEMP_MULTIPLY; int minTemp = Integer.valueOf(System.getenv("AL_MIN_TEMP")) * TEMP_MULTIPLY; BufferedReader reader = null; try { String urlTemplate = "https://api.forecast.io/forecast/%s/%s,%s"; URL url = new URL(String.format(urlTemplate, API_KEY, System.getenv("AL_LAT"), System.getenv("AL_LON"))); InputStreamReader streamReader = new InputStreamReader(url.openStream()); reader = new BufferedReader(streamReader); JSONParser jsonParser = new JSONParser(); try { JSONObject jsonObject = (JSONObject) jsonParser.parse(reader); JSONArray hourly = (JSONArray) ((JSONObject) jsonObject.get("hourly")).get("data"); for (int i = START_HOUR; i < END_HOUR; i++) { JSONObject hour = (JSONObject) hourly.get(i); int temperature = safeIntFromJson(hour, "apparentTemperature", TEMP_MULTIPLY); if (temperature > maxTemp) { temperature = maxTemp; } else if (temperature < minTemp) { temperature = minTemp; } else { float tempFloat = (float) 100 / (maxTemp - minTemp) * (temperature - minTemp); temperature = (int) (tempFloat * TEMP_MULTIPLY); } int wind = safeIntFromJson(hour, "windSpeed", WIND_MULTIPLY); int precip = safeIntFromJson(hour, "precipIntensity", PRECIP_MULTIPLY); data.write(intToBytes(temperature)); data.write(intToBytes(wind)); data.write(intToBytes(precip)); } } catch (ParseException e) { e.printStackTrace(); } } finally { try { if (reader != null) reader.close(); } catch (IOException ioe) { ioe.printStackTrace(); } } } private byte[] intToBytes(int value) { return ByteBuffer.allocate(4).putInt(value).array(); } private int safeIntFromJson(final JSONObject data, final String dataKey, final int multiply) throws IOException { Object jsonAttrValue = data.get(dataKey); if (jsonAttrValue instanceof Long) { return (int) ((Long) jsonAttrValue * multiply); } else { return (int) ((Double) jsonAttrValue * multiply); } } } 

It is necessary to clarify what 5 properties mean, the values ​​of which we request in runtime:

AL_API_KEY - Developer’s secret key forecast.io
AL_LAT, AL_LON - Coordinates
AL_MAX_TEMP, AL_MIN_TEMP - Values ​​of the minimum and maximum temperatures for a given area. This is necessary not to waste some areas in the used color range: let's say in my area (Texas, USA) - the temperature never drops below zero, and I would like the purple color (the lowest one in our palette) to be 0 and not -25, as could be set for Moscow. Thus, our service does not return real temperature - it returns one hundredth percent between AL_MIN_TEMP & AL_MAX_TEMP.

The source code of the web application along with the maven assembly file is available in the github.com/manusovich/aladdin-service repository.

Next we need any hosting for our Java web application. I used my favorite heroku , but you can use any other. The repository already contains the file needed to run the application in the heroku environment called Procfile.

So, if we use heroku, all we need to do is:


Now our servlet can be executed by opening the https://aladdin-service.herokuapp.com/forecast link in the browser. This will return a file with the weather forecast (168 bytes in size) for the specified location (property for the heroku application)

Software on the lamp side


First of all, it is necessary to decide how we will send a signal to our LED strip. The tape uses a WS2811 chip to control the LED. After a brief search, I came across a tutorial from Adafruit - learn.adafruit.com/neopixels-on-raspberry-pi , where I found reference to the rpi_ws281x library, which just allows you to generate a signal for a tape based on WS281x chips.

I fork the library into my repository and added the necessary code to main.c (see below in the lamp controller section) to simplify development to a minimum.

It is necessary to make a small digression and tell how I usually develop code for my projects based on Raspberry Pi. I found editing code through ssh completely uncomfortable. Copy code permanently via ssh too. So I just create a GitHub repository, upload all the code there and use my favorite IDE for development. On the Raspberry Pi side, I create a shell script, which every 10 seconds tries to get changes from the repository. If they are, the script stops the program execution, downloads updates, compiles everything and starts the program. The script is hung up on autoload. This allows you to develop code remotely and at the same time speeds up the process of checking changes on the device. But at the same time loads the wifi network. When software development is complete, I make the update period longer — for example, 60 minutes and leave it forever in this state.

The algorithm thus turns out the following:


Configure RaspberryPi



This script updates the forecast, launches the application, as well as in the background, updates the weather forecast every 10 minutes and every 60 minutes checks the project repository for changes. If there are changes, they are taken from the repository, compiled and run.

Script code
 #!/bin/bash echo "Read forecast" curl https://aladdin-service.herokuapp.com/forecast > /home/pi/rpi_ws281x/forecast echo "Kill old instance..." pkill test echo "Run new instance..." exec /home/pi/rpi_ws281x/test & echo "Start pooling for changes" C=0 while true; do C=$((C+1)) # once per 10 minutes if [ $((C%60)) -eq 0 ] then echo "Update forecast... " curl https://aladdin-service.herokuapp.com/forecast > /home/pi/rpi_ws281x/forecast fi # once per one hour if [ $((C%360)) -eq 0 ] then echo "Check repository... " cd /home/pi/rpi_ws281x git fetch > build_log.txt 2>&1 if [ -s build_log.txt ] then echo "Application code has been changed. Getting changes..." cd /home/pi/rpi_ws281x git pull echo "Bulding application..." scons echo "Kill old application..." pkill test echo "Launch new application..." exec /home/pi/rpi_ws281x/test & echo "Done" else echo "No changes in the repository ($N)" fi fi sleep 10s done 

Some points should be clarified:

  1. Absolute paths - this script will be run from autorun and we need to specify all paths. Thus, it turns out that on the Raspberry Pi our repository should be cloned into the / home / pi / rpi_ws281x directory. If you have another way, you will need to update this shell script.
  2. This script must be run as administrator, as the ribbon control code uses direct memory access and must be run as administrator.

Lamp controller


Now let's look at the LED control code on the LED strip. This code is in the main.c file and is an infinite loop and a set of procedures for changing the color of the LEDs.

The main program method contains the library's initialization of the rpi_ws281x library for working with LED tape and starts an infinite loop on the drawing of states:

Main method code
 int main(int argc, char *argv[]) { int frames_per_second = 30; int ret = 0; setup_handlers(); if (ws2811_init(&ledstring)) { return -1; } long c = 0; update_forecast(); matrix_render_forecast(); while (1) { matrix_fade(); matrix_render_wind(); matrix_render_precip(c); matrix_render(); if (ws2811_render(&ledstring)) { ret = -1; break; } usleep((useconds_t) (1000000 / frames_per_second)); c++; if (c % (frames_per_second * 60 * 5) == 0) { // each 5 minutes update forecast update_forecast(); } } ws2811_fini(&ledstring); return ret; } 

The update_forecast method reads the current weather forecast from the file / home / pi / rpi_ws281x / forecast

The matrix_render_forecast method fills in the matrix with the current values ​​of the weather forecast. In this case, we use a palette of 23 colors taken from the site paletton.com :

 ws2811_led_t dotcolors[] = { 0x882D61, 0x6F256F, 0x582A72, 0x4B2D73, 0x403075, 0x343477, 0x2E4272, 0x29506D, 0x226666, 0x277553, 0x2D882D, 0x609732, 0x7B9F35, 0x91A437, 0xAAAA39, 0xAAA039, 0xAA9739, 0xAA8E39, 0xAA8439, 0xAA7939, 0xAA6C39, 0xAA5939, 0xAA3939 }; 

The matrix_fade method suppresses any color variations from the predicted temperature.

The matrix_render_wind method draws excitement that moves horizontally back and forth at a speed that is equal to wind speed * per coefficient.

The matrix_render_precip method draws precipitations along the edges of levels. He needs a total counter, since the total update rate is 30 frames per second and this turned out to be very fast in order to change colors. Therefore, we do this only 15 times per second.

All rendering goes to the XRGB matrix [WIDTH] [HEIGHT]. We need the XRGB structure to store floating point numbers instead of integer colors. This allows us to increase the smoothness of transitions and directly convert to RGB in the matrix_render method.

When launched, the program displays the current forecast value (temperature, wind and precipitation) to the console. It should be noted that the temperature value is a base point (one hundredth of a percent).

 pi@raspberrypi ~/rpi_ws281x $ sudo ./test Temp: 5978, Wind: 953, Precip: 0 Temp: 5847, Wind: 1099, Precip: 0 Temp: 5744, Wind: 1157, Precip: 0 Temp: 5657, Wind: 1267, Precip: 0 Temp: 5612, Wind: 1249, Precip: 1 Temp: 5534, Wind: 1357, Precip: 1 Temp: 5548, Wind: 1359, Precip: 0 Temp: 5605, Wind: 1378, Precip: 0 Temp: 5617, Wind: 1319, Precip: 0 Temp: 5597, Wind: 1281, Precip: 0 Temp: 5644, Wind: 1246, Precip: 0 Temp: 5667, Wind: 1277, Precip: 0 

Alternative modes of operation


You can consider the resulting product as a platform to display anything. At your disposal there are about 300 independent LEDs and you decide what can be displayed there. For example, you can organize the display of assembly states on the continuous integration server or simply make an unusual lamp that will play with the colors of the rainbow like in the next video.



Estimate and conclusion


Power supply 5 volts 10 amps - $ 25
2 meters of RGB tape (144 LEDs per meter) - $ 78
Raspberry Pi - $ 30
Edimax Wifi USB - $ 8
3D printing of frames for LED strip - $ 15 for PLA plastic
Lamp donor - $ 35

In total, the total cost of the product turned out to be about $ 200 for home manufacturing.
I hope this article will be useful to you. If you have any questions, feel free to ask in the comments.

Source: https://habr.com/ru/post/251299/


All Articles