Weather Audio Notifier

Create an auditory weather notification project.

Introduction

This tutorial demonstrates how to use the Arduino Zero and the WiFi Shield 101 to act as a web client and parse Json formatted text. Json is well-known data-interchange format which is often used for communication between a server and client, and is particularly useful owing to its easy-to-read format and simplicity to parse and generate. In this example, weather information from openweathermap.org is used to display the current weather information. This is then periodically compared with weather information from the following hours.

Goals

  • About Json.
  • Use weather data to create an audio notification when the weather changes.

Hardware & Software Needed

The Circuit

The red wire of the piezo is connected to digital pin 8, and the black wire to ground. Optionally, the audio can be improved by using preloaded .wav files instead of the

tone()
function, in which case the circuit from this audio player example can be substituted (with the addition of the WiFi Shield 101).

The circuit for this tutorial.
The circuit for this tutorial.

In the image above, the Arduino Zero board would be stacked below the WiFi Shield 101.

Installing Libraries

The ArduinoJson library can installed from Arduino IDE's library manager. To do this, open the Arduino IDE, go to Tools-> Manage Libraries.. There you can search ArduinoJson and install the library shown. The 'more info' link will take you to the GitHub page which includes all the documentation for the library. For a more detailed explanation on installing and importing libraries see this tutorial.

Parsing a Json

In this tutorial we use openweathermap.org to provide the Json information. An example of the raw information used can be found here, this is the API from openweathermap and the city can be changed in the URL.

Using the Json below as an example, we can see that it contains a lot of information, including the city, coordinates, rain, temperature, wind speeds, etc.

1{"city":{"id":3165524,"name":"Torino","coord":{"lon":7.68682,"lat":45.070492},"country":"IT","population":0,"sys":{"population":0}},"cod":"200","message":0.0066,"cnt":2,"list":[{"dt":1442404800,"main":{"temp":22.11,"temp_min":18.61,"temp_max":22.11,"pressure":989.45,"sea_level":1023.92,"grnd_level":989.45,"humidity":89,"temp_kf":3.5},"weather":[{"id":500,"main":"Rain","description":"light rain","icon":"10d"}],"clouds":{"all":80},"wind":{"speed":1.89,"deg":17.5001},"rain":{"3h":0.095},"sys":{"pod":"d"},"dt_txt":"2015-09-16 12:00:00"},{"dt":1442415600,"main":{"temp":22.93,"temp_min":19.62,"temp_max":22.93,"pressure":988.09,"sea_level":1022.61,"grnd_level":988.09,"humidity":79,"temp_kf":3.3},"weather":[{"id":804,"main":"Clouds","description":"overcast clouds","icon":"04d"}],"clouds":{"all":92},"wind":{"speed":2.01,"deg":29.502},"rain":{},"sys":{"pod":"d"},"dt_txt":"2015-09-16 15:00:00"}]}

It is useful to view this information as a tree using an online Json formatter so that we can easily see which of the nested objects/arrays contains the information in which we are interested. The following image shows the tree view of the previous Json. It is easy to see that inside the root object 'JSON', there is an object called 'city' and an array called 'list' which contains two objects; '0' and '1'. the '0' array contains current weather information and '1' contains later weather information.

If we then open further the 'main' and 'weather' fields, we can see that main contains various information and that 'weather' contains a further object called [0]. The information accessed in this example is 'temp' and 'humidity' which are inside 'main', and 'description' which is inside '0' inside 'weather', all of which are found in both [0] and [1] from the 'list' array.

Json Tree View
Json Tree View

The part of the code that deals with parsing the information from the Json is seen in the following block. From the tree view, we can see that there is a root object which represents the entire Json and corresponds to the the 'root'

JsonObject
in the code. From there, we can access the list array with
'root["list"]'
. It can be seen that inside the list there are two objects;
[0]
and
[1]
, which correspond to 'now' and 'later' JsonObjects. Inside each of the objects
now
and
later
, there is an object called
main
and and an array called weather , inside which there is an object called
[0]
which contains the description. We are interested in the information inside
'main'
and
'weather -> [0]'
for both now and later, specifically, temperature, humidity and description. Therefore we can use the following code to access this information and store it either as a String or as a float. We can also access the city name directly from the root:

Note: Arduino Json library provides a syntax which allows you to navigate the tree starting from a JsonObject and searching the values by their labels.

1JsonArray& list = root["list"];
2
3JsonObject& now = list[0];
4
5JsonObject& later = list[1];
6
7String city = root["city"]["name"];
8float tempNow = now["main"]["temp"];
9float humidityNow = now["main"]["humidity"];
10
11String weatherNow = now["weather"][0]["description"];
12
13float tempLater = later["main"]["temp"];
14float humidityLater = later["main"]["humidity"];
15
16String weatherLater = later["weather"][0]["description"];

Code

The basic concept of this program is that it parses six fields from the Json; humidity, temperature and description for both now and later and compares them at an interval of 10 minutes (can be changed to a shorter period for testing). At the beginning of the sketch, you must manually enter the network name of your Wireless network, the password for this network and the city name and country code without spaces, for example: "NewYork,US".

1char ssid[] = "ssid"; // your network SSID (name)
2char pass[] = "password"; // your network password (use for WPA, or use as key for WEP)
3int keyIndex = 0; // your network key Index number (needed only for WEP)
4
5String nameOfCity = "Turin,IT"; // your city of interest here in format "city,countrycode"

The melodies which will be played (for either a positive or negative weather change) are instantiated in the following block where the notes and note durations are defined for later:

1int posMelody[] = {330,415,494,659}; //E3,G#3,B3,E4
2int negMelody[] = {392,370,349,330}; //G3,F#3,F3,E3
3int noteDurations[] = {4, 4, 4, 8}; //Will correspond to note lengths 8th,8th,8th,4th

A statement to check if 10 minutes have passed is executed inside the loop, and if so a http request is made.

1if (millis() - lastConnectionTime > postingInterval) {
2
3 // note the time that the connection was made:
4
5 lastConnectionTime = millis();
6
7 httpRequest();
8
9 }

After that, there is a check to see whether there are incoming bytes available, which will only happen once every 10 minutes after the http request. Since it is known that a Json message is a nest of curly brackets and that for each open bracket there must be a close bracket, we can deduce that the message starts at the first curly bracket in the stream and ends when the number of open brackets - close brackets = 0. Therefore, the following code waits for the first curly bracket and then sets a variable

startJson
to
1
, indicating that the message has started, and increments a variable endResponse which decrements each time the incoming byte is a close bracket. Then, if
startJson
is true, i.e the message has started, the incoming byte is appended to the string text. If
endResponse = 0
indicating that there were an equal number of close and open brackets, then the message is over, providing that it started (startJson = 1). When both these conditions are met, then the Json is ready to be parsed and the string is sent to the
parseJson()
function.

1if (client.available()) {
2
3 c = client.read();
4
5 // json contains equal number of open and close curly brackets, therefore by counting
6
7 // the open and close occurrences, we can determine when a json is completely received
8
9 // endResponse == 0 means equal number of open and close curly brackets reached
10
11 if (endResponse == 0 && startJson == true) {
12
13 parseJson(text.c_str()); // parse c string text in parseJson function
14
15 text = ""; // clear text string for the next time
16
17 startJson = false; // set startJson to false to indicate that a new message has not yet started
18
19 }
20
21 if (c == '{') {
22
23 startJson = true; // set startJson true to indicate json message has started
24
25 endResponse++;
26
27 }
28
29 if (c == '}') {
30
31 endResponse--;
32
33 }
34
35 if (startJson == true) {
36
37 text += c;
38
39 }
40
41 }

The function

printDiffFloat()
compares the two values of the now and later floats and if there is a difference and if so, the difference is printed on the serial monitor. If there is no change, the return; exits the function and nothing is printed nor played.

1void printDiffFloat(float now, float later, String parameter, String unit) {
2
3 String change;
4
5 if (now > later) { //if parameter is higher now than later
6
7 change = "drop from ";
8
9 }
10
11 else if (now < later) { //else if parameter is higher later than now
12
13 change = "rise from ";
14
15 }
16
17 else { //else there is no difference
18
19 return; //exit function printDiffFloat
20
21 }
22
23 Serial.print("UPDATE: The " + parameter + "will " + change); //print change
24
25 Serial.print(now);
26
27 Serial.print(unit + " to ");
28
29 Serial.print(later);
30
31 Serial.println(unit + "!");
32}

The function

printDiffString()
checks for keywords inside the now and later strings. If the index of a word such as "rain" is not found because it does not exist inside the string, then the value of the int is set to
-1
. We can then check to see if the word was not in the string of current data but does exist in the string for the future data
(int != -1)
, and if so we can send a notification, either the positive or negative short melody depending on the change and the information of the change is printed in the serial monitor. Note that the search for clear is in a different statement than for words rain, snow and hail so that a positive notification is sounded instead of negative.

1void printDiffString(String now, String later, String weatherType) {
2
3 int indexNow = now.indexOf(weatherType);
4
5 int indexLater = later.indexOf(weatherType);
6
7 //for all types of weather except for clear skies, if the current weather does not contain the weather type and the later message does, send notification
8
9 if (weatherType != "clear") {
10
11 if (indexNow == -1 && indexLater != -1) {
12
13 Serial.println("Oh no! It is going to " + weatherType + " later! Predicted " + later);
14
15 for (int thisNote = 0; thisNote < 4; thisNote++) {
16
17 int noteDuration = 1000 / noteDurations[thisNote];
18
19 tone(8, negMelody[thisNote], noteDuration); //play negative melody through piezo
20
21 }
22
23 }
24
25 }
26
27 //for clear skies, if the current weather does not contain the word clear and the later message does, send notification that it will be sunny later
28
29 else {
30
31 if (indexNow == -1 && indexLater != -1) {
32
33 Serial.println("It is going to be sunny later! Predicted " + later);
34
35 for (int thisNote = 0; thisNote < 4; thisNote++) {
36
37 int noteDuration = 1000 / noteDurations[thisNote];
38
39 tone(8, posMelody[thisNote], noteDuration); //play positive melody through piezo
40
41 }
42
43 }
44
45 }
46}

The full sketch can be seen below:

1/*
2
3Weather Audio Notifier
4
5Hardware Required:
6
7* Arduino Zero Board
8
9* Arduino WIFI Shield 101
10
11
12* Piezo
13
14Software Required:
15
16* ArduinoJson Library
17
18 created Sept 2015
19
20 by Helena Bisby <support@arduino.cc>
21
22This example code is in the public domain
23
24http://arduino.cc/en/Tutorial/WeatherAudioNotifier
25
26
27
28*/
29
30#include <SPI.h>
31#include <WiFi101.h>
32#include <ArduinoJson.h>
33
34#define JSON_BUFF_DIMENSION 2500
35#include "arduino_secrets.h"
36///////please enter your sensitive data in the Secret tab/arduino_secrets.h
37char ssid[] = SECRET_SSID; // your network SSID (name)
38char pass[] = SECRET_PASS; // your network password (use for WPA, or use as key for WEP)
39int keyIndex = 0; // your network key Index number (needed only for WEP)
40
41String nameOfCity = "Turin,IT"; // your city of interest here in format "city,countrycode"
42
43String text;
44int endResponse = 0;
45boolean startJson = false;
46int posMelody[] = {330, 415, 494, 659}; //E3,G#3,B3,E4
47int negMelody[] = {392, 370, 349, 330}; //G3,F#3,F3,E3
48int noteDurations[] = {4, 4, 4, 8}; //Will correspond to note lengths 8th,8th,8th,4th
49int status = WL_IDLE_STATUS;
50
51const char server[] = "api.openweathermap.org"; // name address for openweathermap (using DNS)
52
53WiFiClient client;
54unsigned long lastConnectionTime = 10 * 60 * 1000; // last time you connected to the server, in milliseconds
55
56const unsigned long postingInterval = 10 * 60 * 1000; // posting interval of 10 minutes (10L * 1000L; 10 seconds delay for testing)
57
58void setup() {
59
60 //Initialize serial and wait for port to open:
61
62 Serial.begin(9600);
63
64 text.reserve(JSON_BUFF_DIMENSION);
65
66 // check for the presence of the shield:
67
68 if (WiFi.status() == WL_NO_SHIELD) {
69
70 Serial.println("WiFi shield not present");
71
72 // don't continue:
73
74 while (true);
75
76 }
77
78 // attempt to connect to Wifi network:
79
80 while ( status != WL_CONNECTED) {
81
82 Serial.print("Attempting to connect to SSID: ");
83
84 Serial.println(ssid);
85
86 // Connect to WPA/WPA2 network. Change this line if using open or WEP network:
87
88 status = WiFi.begin(ssid, pass);
89
90 // wait 10 seconds for connection:
91
92 delay(10000);
93
94 }
95
96 // you're connected now, so print out the status:
97
98 printWifiStatus();
99}
100
101void loop() {
102
103 // if ten minutes have passed since your last connection,
104
105 // then connect again and send data:
106
107 if (millis() - lastConnectionTime > postingInterval) {
108
109 // note the time that the connection was made:
110
111 lastConnectionTime = millis();
112
113 httpRequest();
114
115 }
116
117 char c = 0;
118
119 if (client.available()) {
120
121 c = client.read();
122
123 // json contains equal number of open and close curly brackets, therefore by counting
124
125 // the open and close occurrences, we can determine when a json is completely received
126
127
128
129 // endResponse == 0 means equal number of open and close curly brackets reached
130
131 if (endResponse == 0 && startJson == true) {
132
133 parseJson(text.c_str()); // parse c string text in parseJson function
134
135 text = ""; // clear text string for the next time
136
137 startJson = false; // set startJson to false to indicate that a new message has not yet started
138
139 }
140
141 if (c == '{') {
142
143 startJson = true; // set startJson true to indicate json message has started
144
145 endResponse++;
146
147 }
148
149 if (c == '}') {
150
151 endResponse--;
152
153 }
154
155 if (startJson == true) {
156
157 text += c;
158
159 }
160
161 }
162}
163void parseJson(const char * jsonString) {
164
165 StaticJsonBuffer<4000> jsonBuffer;
166
167 // FIND FIELDS IN JSON TREE
168
169 JsonObject& root = jsonBuffer.parseObject(jsonString);
170
171 if (!root.success()) {
172
173 Serial.println("parseObject() failed");
174
175 return;
176
177 }
178
179 JsonArray& list = root["list"];
180
181 JsonObject& now = list[0];
182
183 JsonObject& later = list[1];
184
185 String city = root["city"]["name"];
186
187 float tempNow = now["main"]["temp"];
188
189 float humidityNow = now["main"]["humidity"];
190
191 String weatherNow = now["weather"][0]["description"];
192
193 float tempLater = later["main"]["temp"];
194
195 float humidityLater = later["main"]["humidity"];
196
197 String weatherLater = later["weather"][0]["description"];
198
199 printDiffFloat(tempNow, tempLater, "temperature", "*C");
200
201 printDiffString(weatherNow, weatherLater, "rain");
202
203 printDiffString(weatherNow, weatherLater, "snow");
204
205 printDiffString(weatherNow, weatherLater, "hail");
206
207 printDiffString(weatherNow, weatherLater, "clear");
208
209 printDiffFloat(humidityNow, humidityLater, "humidity", "%");
210
211 Serial.println();
212
213}
214
215// this method makes a HTTP connection to the server:
216void httpRequest() {
217
218 // close any connection before send a new request.
219
220 // This will free the socket on the WiFi shield
221
222 client.stop();
223
224 // if there's a successful connection:
225
226 if (client.connect(server, 80)) {
227
228 // Serial.println("connecting...");
229
230 // send the HTTP PUT request:
231
232 client.println("GET /data/2.5/forecast?q=" + nameOfCity + "&mode=json&units=metric&cnt=2 HTTP/1.1");
233
234 client.println("Host: api.openweathermap.org");
235
236 client.println("User-Agent: ArduinoWiFi/1.1");
237
238 client.println("Connection: close");
239
240 client.println();
241
242 }
243
244 else {
245
246 // if you couldn't make a connection:
247
248 Serial.println("connection failed");
249
250 }
251}
252
253void printDiffString(String now, String later, String weatherType) {
254
255 int indexNow = now.indexOf(weatherType);
256
257 int indexLater = later.indexOf(weatherType);
258
259 // for all types of weather except for clear skies, if the current weather does not contain the weather type and the later message does, send notification
260
261 if (weatherType != "clear") {
262
263 if (indexNow == -1 && indexLater != -1) {
264
265 Serial.println("Oh no! It is going to " + weatherType + " later! Predicted " + later);
266
267 for (int thisNote = 0; thisNote < 4; thisNote++) {
268
269 int noteDuration = 1000 / noteDurations[thisNote];
270
271 tone(8, negMelody[thisNote], noteDuration); // play negative melody through piezo
272
273 }
274
275 }
276
277 }
278
279 // for clear skies, if the current weather does not contain the word clear and the later message does, send notification that it will be sunny later
280
281 else {
282
283 if (indexNow == -1 && indexLater != -1) {
284
285 Serial.println("It is going to be sunny later! Predicted " + later);
286
287 for (int thisNote = 0; thisNote < 4; thisNote++) {
288
289 int noteDuration = 1000 / noteDurations[thisNote];
290
291 tone(8, posMelody[thisNote], noteDuration); // play positive melody through piezo
292
293 }
294
295 }
296
297 }
298}
299
300void printDiffFloat(float now, float later, String parameter, String unit) {
301
302 String change;
303
304 if (now > later) {
305
306 change = "drop from ";
307
308 }
309
310 else if (now < later) {
311
312 change = "rise from ";
313
314 }
315
316 else {
317
318 return;
319
320 }
321
322 Serial.print("UPDATE: The " + parameter + " will " + change);
323
324 Serial.print(now);
325
326 Serial.print(unit + " to ");
327
328 Serial.print(later);
329
330 Serial.println(unit + "!");
331}
332
333void printWifiStatus() {
334
335 // print the SSID of the network you're attached to:
336
337 Serial.print("SSID: ");
338
339 Serial.println(WiFi.SSID());
340
341 // print your WiFi shield's IP address:
342
343 IPAddress ip = WiFi.localIP();
344
345 Serial.print("IP Address: ");
346
347 Serial.println(ip);
348
349 // print the received signal strength:
350
351 long rssi = WiFi.RSSI();
352
353 Serial.print("signal strength (RSSI):");
354
355 Serial.print(rssi);
356
357 Serial.println(" dBm");
358}

Testing It Out

After you have uploaded the code, if there is a change in weather conditions of the selected city of interest defined in

String nameOfCity = "cityname,countrycode"
; an update of the changes is written to the serial monitor and the piezo will generate an audio notification depending on the result of the weather change.

Troubleshoot

If the code is not working, there are some common issues we can troubleshoot:

  • You have not installed the ArduinoJson library.
  • You have entered the incorrect
    ssid
    or
    pass
    of your network.

Conclusion

In this example, we have learned how to create a weather notification project that notifies you when weather conditions change! All of this is possible because of Json and the ArduinoJson library. Now that you have finished this tutorial, you can start to use Json for other cool applications and projects.

Suggest changes

The content on docs.arduino.cc is facilitated through a public GitHub repository. If you see anything wrong, you can edit this page here.

License

The Arduino documentation is licensed under the Creative Commons Attribution-Share Alike 4.0 license.