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.
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).
Live Weather Station Data
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(Download)
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.