Arduino Weather Station Project - Web Client

Weather Station Web Server Client (Part 10)

In the last part of this project we configure the weather station controller as a web client. We send the data from the sensors connected to the controller to a web server via the Internet. The web server receives the data, validates it and then stores it into a database. We can then access the data realtime via a web browser.

Arduino Web Client to cloud Server

Live Data from Arduino Weather Station

Click on the button below to view the live data from the Test Weather Station. The station is located in Perth, Western Australia and it uses local Perth time (GMT+8).



Arduino Controller Software

To connect to the remote web server we need to change the previous sketch from a web server to a web client. Instead of listening for remote connections the controller now establishes a connection to the remote server. It issues a POST request to the server with the station data in the request body. The data is formated using JSON (JavaScript Object Notation). This is a simple human readable format.

Software Sketch

The web client connects to the remote server every 60 seconds. The sketch uses a counter (timerMinCount) that counts the number of 2.5 timer periods that are used to sample the wind speed. Once the count is reached a connection is made to the server. The web server can be configured using a static IP address or a DHCP assigned IP address. However in this sketch we are using a static IP address.

Lines 108 - 152 is where the data transfer to the server happens. To post data to the server we encode it into a JSON format. This is a done in lines 117 to 129.

To assist with the testing of the weather station we turn the red led on prior to data transmission and off at the end. The actual connection to the server takes place from line 133 to 143.

In the case here we are connecting to the server data.nevixa.com.au (This a private service) on port 80. We are use a http POST operation to send the data to the url endpoint of /aws/feed/NVX6023F1 using HTTP version 1.1. We need to include two headers with the post. These are x-feed-id and x-api-key which are used for authentication of the request by the server.

We tell the server that the body of the message is encoded using application/json and the charset is utf-8. We need to tell the server the length of the body which is done by sending the Content-Length header along with the actual length which is defined by data.length(). All the magic happens at line 142 where we send the json encoded data which is stored in the variable called data.

On completion of sending the data we close the connection to the server using client.stop();

There are many public services available for sending data to. We use our own as we have the full flexability to configure how we handle the data, process it and then view it.


Arduino Weather Station Basic Web Server Sketch
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
#include <SPI.h>
#include <Ethernet.h>

#include "TimerOne.h"
#include <math.h>

#include "cactus_io_DS18B20.h"
#include "cactus_io_BME280_I2C.h"

#define Bucket_Size 0.01 // bucket size to trigger tip count
#define RG11_Pin 3 // digital pin RG11 connected to
#define TX_Pin 8 // used to indicate web data tx
#define DS18B20_Pin 9 // DS18B20 Signal pin on digital 9

#define WindSensor_Pin (2) // digital pin for wind speed sensor
#define WindVane_Pin (A2) // analog pin for wind direction sensor
#define VaneOffset 0 // define the offset for caclulating wind direction

volatile unsigned long tipCount; // rain bucket tip counter used in interrupt routine
volatile unsigned long contactTime; // timer to manage any rain contact bounce in interrupt routine

volatile unsigned int timerCount; // used to count ticks for 2.5sec timer count
volatile unsigned int timerMinCount; // used to determine one minute count
volatile unsigned long rotations; // cup rotation counter for wind speed calcs
volatile unsigned long contactBounceTime; // timer to avoid contact bounce in wind speed sensor

long lastTipcount; // keep track of bucket tips
float totalRainfall; // total amount of rainfall detected

volatile float windSpeed;
int vaneValue; // raw analog value from wind vane
int vaneDirection; // translated 0 - 360 wind direction
int calDirection; // calibrated direction after offset applied
int lastDirValue; // last recorded direction value

float minTemp; // keep track of minimum recorded temp
float maxTemp; // keep track of maximum recorded temp

// Create DS18B20, BME280 object
DS18B20 ds(DS18B20_Pin); // on digital pin 9
BME280_I2C bme; // I2C using address 0x77

// Here we setup the web server. We are using a static ip address and a mac address
byte mac[] = { 0xDE, 0xAD, 0xBE, 0xEF, 0xFE, 0xED };
IPAddress ip(192, 168, 1, 45);
EthernetClient client; // create ethernet client

void setup() {

// setup rain sensor values
lastTipcount = 0;
tipCount = 0;
totalRainfall = 0;

// setup anemometer values
lastDirValue = 0;
rotations = 0;

// setup timer values
timerCount = 0;
timerMinCount = 0;

ds.readSensor();
delay(3000); // allow 3 seconds for sensor to settle down
ds.readSensor(); // read again to avoid weird values for defaults
minTemp = ds.getTemperature_C();
maxTemp = ds.getTemperature_C();

// disable the SD card by switching pin 4 High
pinMode(4, OUTPUT);
digitalWrite(4, HIGH);

// start the Ethernet connection and server
Ethernet.begin(mac, ip);

if (!bme.begin()) {
// Serial.println("Could not find BME280 sensor, check wiring!");
while (1);
}

pinMode(TX_Pin, OUTPUT);
pinMode(RG11_Pin, INPUT);
pinMode(WindSensor_Pin, INPUT);
attachInterrupt(digitalPinToInterrupt(RG11_Pin), isr_rg, FALLING);
attachInterrupt(digitalPinToInterrupt(WindSensor_Pin), isr_rotation, FALLING);

// setup the timer for 0.5 second
Timer1.initialize(500000);
Timer1.attachInterrupt(isr_timer);

sei();// Enable Interrupts
}

void loop() {

ds.readSensor();
bme.readSensor();

// update rainfall total if required
if(tipCount != lastTipcount) {
cli(); // disable interrupts
lastTipcount = tipCount;
totalRainfall = tipCount * Bucket_Size;
sei(); // enable interrupts
}

// if one minute timer is up then send data to server
if(timerMinCount > 23) {
//reset the timer and rain tip count
cli(); // disable interrupts
timerMinCount = 0;
tipCount = 0;
sei(); // enable interrupts

getWindDirection();

String data = "{\"t\":\"";
data += ds.getTemperature_C();
data += "\",\"h\":\"";
data += bme.getHumidity();
data += "\",\"p\":\"";
data += bme.getPressure_MB();
data += "\",\"ws\":\"";
data += windSpeed;
data += "\",\"wd\":\"";
data += calDirection;
data += "\",\"r\":\"";
data += totalRainfall;
data += "\"}";

digitalWrite(TX_Pin,HIGH); // Turn on red tx led

if (client.connect("data.nevixa.com.au",80)) {
client.println("POST /aws/feed/NVX6023F1 HTTP/1.1");
client.println("Host: data.nevixa.com.au");
client.println("x-feed-id: zzzzzzzzzzzzz");
client.println("x-api-key: xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx");
client.println("content-Type: application/json;charset=utf-8");
client.print("Content-Length: ");
client.println(data.length());
client.println();
client.println(data);
}

delay(1000);
digitalWrite(TX_Pin,LOW); // Turn off red tx led
// stop the connection:
if(client.connected()) {
client.stop(); // Disconnect from the server
}
}
}

// Interrupt handler routine for timer interrupt
void isr_timer() {

timerCount++;

if(timerCount == 5) {
// convert to mp/h using the formula V=P(2.25/T)
// V = P(2.25/2.5) = P * 0.9
windSpeed = rotations * 0.9;
rotations = 0;
timerCount = 0;
timerMinCount++; // increment 1 minute count
}
}

// Interrupt handler routine that is triggered when the rg-11 detects rain
void isr_rg() {

if((millis() - contactTime) > 15 ) { // debounce of sensor signal
tipCount++;
contactTime = millis();
}
}

// Interrupt handler routine to increment the rotation count for wind speed
void isr_rotation() {

if((millis() - contactBounceTime) > 15 ) { // debounce the switch contact
rotations++;
contactBounceTime = millis();
}
}

// Get Wind Direction
void getWindDirection() {

vaneValue = analogRead(WindVane_Pin);
vaneDirection = map(vaneValue, 0, 1023, 0, 360);
calDirection = vaneDirection + VaneOffset;

if(calDirection > 360)
calDirection = calDirection - 360;

if(calDirection > 360)
calDirection = calDirection - 360;
}

Remote Server Software

The server that we post the data to (data.nevixa.com.au) is running Node.js application. The server authenticates the connection before validating the data. Once the data is authenticated and validated it is then stored in a mysql compatible database.

Browser Client

To view the data in this case we use http://cactus.io/live/aws/cio6019 or just click on the button below. This page displays the basic framework (header, footer) before calling a javascript script that retrieves the data from the remote server. Using another javascript library (nvx-chart-min.js) it displays the data using SVG (Scalable Vector Graphics). This involves drawing and scaling the axis. It then scales and draws the polylines that are used to represent the temperature, humidty and barometric pressure. Rainfall is displayed using vertical lines from the bottom of the chart. We wrote our own library because for functionality reasons only.


Conclusion

This is the last step for this project. We are in the process of putting together another weather station that is solar powered and uses a GSM connection. It also includes a tipping bucket rain sensor for more accurate detection of precipitation.



License