Weather API Magic: Building a UDP Server with Node.js and JavaScript

Well, I looked around a lot and couldn’t find anything on here about actually setting up a UDP server with Node.js and JavaScript. I found lots of awesome python resources, and was able to guess from all the codes I found how I can make a basic local Node.js API server that served the UDP data locally. So when the internet goes out, I still have my data.
Here is the code:


:warning: These instructions reference the Linux terminal. If you are using Windows, macOS, or another OS that is not a Linux distribution, please familiarize yourself with the appropriate terminal commands for your OS.

1. Install Node.js

If you are using Linux, you can type:

sudo apt install npm

in the terminal. The command (in Linux) installs NPM on your device.

Otherwise, you can download Node.js from https://nodejs.org/.
If you need more help with Node.js, you can check out the docs here:
Introduction to Node.js

2. Next, in the terminal, navigate to the directory that you want to make your API project in. In my case, it was /Coding/API/Example. To do this, type:

cd Coding

in the Linux terminal or use:

mkdir Coding

to make a new directory for your project. Remember to replace “Coding” with the actual directory you want to use. Please note that the terminal is case-sensitive (coding does not = Coding).

3. Now that you are in your API project directory, type

npm init

to start a new Node.js project.

4. You will be prompted by the terminal to input data about the project.
Here is an example of the project setup process:

(Click to expand)

This utility will walk you through creating a package.json file.
It only covers the most common items, and tries to guess sensible defaults.

See `npm help init` for definitive documentation on these fields
and exactly what they do.

Use `npm install <pkg>` afterwards to install a package and
save it as a dependency in the package.json file.

Press ^C at any time to quit.
package name: (my_project) my_project
version: (1.0.0) 1.0.0
description: My API Example
entry point: (index.js) index.js
test command: api
git repository: none
keywords: none
author: Best_codes
license: (ISC) ISC
About to write to /home/<username>/Coding/API/my_project/package.json:

{
  "name": "my_project",
  "version": "1.0.0",
  "description": "My API Example",
  "main": "index.js",
  "scripts": {
    "test": "api"
  },
  "repository": {
    "type": "git",
    "url": "none"
  },
  "keywords": [
    "none"
  ],
  "author": "Best_codes",
  "license": "ISC"
}


Is this OK? (yes) yes

Input the data for your project and type yes when prompted with Is this OK? (yes).
npm will then initialize your project.

5. Now that you have a Node.js project set up, you need to configure your API file.
This file will be whatever you typed in when prompted with “entry point: (index.js)” which in my case was index.js. To edit the file, you will need to type:

sudo nano index.js

in the Linux terminal. This will open a basic file editor in the terminal that allows you to edit the index.js file you created. If you don’t have nano installed, you can install it by typing:

sudo apt install nano

in the Linux terminal.

6. In the nano editor, there should be nothing (the file is empty).
This file is the Node.js code file. It is how you change what your API does, what port the API is on, etc. So, we want the API to return relevant data from the UDP port that the Tempest Weather Station is using (this is normally 52000 or something similar). To do this, I used the following code:

const express = require('express');
const dgram = require('dgram');
const cors = require('cors');

const UDP_PORT = 50222; // Port number to listen on

const app = express();
const sock = dgram.createSocket('udp4');

let latestObservationData = null; // Variable to store the latest valid observation data

app.use(cors()); // Enable CORS for all routes

app.get('/weather', (req, res) => {
  res.json({ message: 'Latest UDP observation data', data: latestObservationData });
});

sock.on('message', (data) => {
  const jsonData = JSON.parse(data.toString());

  if (jsonData.type === 'obs_st') {
    // Process observation data
    console.log('Received observation data:', jsonData);
    latestObservationData = jsonData; // Update the latest observation data
  }
});

sock.bind(UDP_PORT, '0.0.0.0', () => {
  console.log(`Listening for UDP data on port ${UDP_PORT}`);
});

app.listen(3000, () => {
  console.log('API server is running on port 3000');
});
What the Code Does

Overall Explanation of the Code

The code sets up an API server using Express.js to expose a single endpoint /weather that returns the latest observation data received via UDP (User Datagram Protocol). The server listens for UDP messages on a specified port and updates the latestObservationData variable whenever a message of type 'obs_st' is received. The server logs the received observation data and the port it is listening on.

Code Structure Overview

  • Dependencies: The code requires the express, cors, and dgram modules, which are imported at the beginning of the code.
  • Constants:
  • UDP_PORT: Specifies the port number on which the UDP socket will listen for incoming messages.
  • Express Setup:
  • app: Creates an instance of the Express application.
  • app.get('/weather'): Defines a route handler for the /weather endpoint that returns the latest observation data as a JSON response.
  • UDP Socket Setup:
  • sock: Creates a UDP socket using the dgram module.
  • sock.on('message'): Defines a callback function to handle incoming UDP messages. It parses the received data as JSON, checks if the message type is 'obs_st', logs the received data, and updates the latestObservationData variable.
  • sock.bind(): Binds the UDP socket to the specified port and IP address (0.0.0.0 indicates any available network interface).
  • Server Start:
  • app.listen(): Starts the Express server on port 3000 and logs a message indicating that the server is running.

To use my code, copy it above and press Ctrl+Shift+V in the nano editor on Linux to paste the code into the nano editor. Then, press Ctrl+O to save the modifications to the file (if you are prompted with “File name to write (index.js):” then press enter). Then press Ctrl+X to exit the nano editor.

7. Now, you need to install the dependencies for your API project (these were in the ‘require’ part of the file). Make sure you are in your project directory, then type the following double-command in the terminal:

npm install express && npm install dgram && npm install cors

This installs the express and dgram dependencies in your project folder, that way the API can reference them when needed. You may get a console warn about dgram, just ignore it.

8. Next, you need to start your API. To do this, run the command:

node index.js

You should receive a message:

Listening for UDP data on port 50222
API server is running on port 3000

9. Wait about 1 minute (sometimes less) for the API to catch data of the UDP data of the correct type. When the API receives the appropriate data fomr the UDP, it will make this message in the console:

Received observation data: {
  serial_number: '[number]',
  type: 'obs_st',
  hub_sn: '[hub_sn]',
  obs: [
    [
      1695493503,   0.6,  2.86,
            4.76,   151,     3,
          972.67, 30.56, 69.79,
           89303,  7.16,   744,
        0.049444,     1,     0,
               0, 2.751,     1
    ]
  ],
  firmware_revision: [version]
}

:warning: Please note that the UDP port broadcasts other data about the Tempest, such as the device status and hub status. Such data is unrelated to the current weather conditions and is not covered here.

10. Now, to access your API data, you will need to open a browser and load the URL:

http://localhost:3000/weather

You should get a response like:

{
  "message": "Latest UDP observation data",
  "data": {
    "serial_number": "ST-xxxxxxxx",
    "type": "obs_st",
    "hub_sn": "HB-xxxxxxxx",
    "obs": [
      [1695493983, 1.32, 3.24, 5.14, 144, 3, 972.55, 30.42, 70.17, 35492, 2.84, 296, 0.033213, 1, 0, 0, 2.751, 1]
    ],
    "firmware_revision": xxx
  }
}

If the API has received the data. (See the chart below for an explanation of what different fields go with each number).

10. Now, to retrieve your data using JS, you can use the following JS code:

async function fetchData(field) {
  try {
    const response = await fetch('http://localhost:3000/weather');
    const data = await response.json();

    const obsData = data.data.obs[0]; // Get the first element in the obs array
    const specifiedField = obsData[field];
    console.log("Got data from field " + field + ": " + specifiedField);
    //Do other things with the value here
    
    return specifiedField;
  } catch (error) {
    console.error('Error fetching or processing data:', error);
  }
}

//to use the function as a test uncomment:
//fetchData(0);

//Example usage
//The data function is async, so the data must always be fetched this way
fetchData(0)
  .then(value => {
    alert(value);
  })
  .catch(error => {
    console.error(error);
  });

Or a rather huge HTML code if you prefer that instead:

(Click to expand)
<!DOCTYPE html>
<html>
<head>
<title>Page Title</title>
</head>
<body>

<div id="dataContainer"></div>

<script>
async function fetchData() {
  const response = await fetch('http://localhost:3000/weather');
  const data = await response.json();

  const obsData = data.data.obs[0]; // Get the first element in the obs array

  const obsLabels = [
    "Time Epoch (Seconds)",
    "Wind Lull (m/s)",
    "Wind Avg (m/s)",
    "Wind Gust (m/s)",
    "Wind Direction (Degrees)",
    "Wind Sample Interval (seconds)",
    "Station Pressure (MB)",
    "Air Temperature (C)",
    "Relative Humidity (%)",
    "Illuminance (Lux)",
    "UV Index",
    "Solar Radiation (W/m^2)",
    "Rain amount over previous minute (mm)",
    "Precipitation Type (0 = none, 1 = rain, 2 = hail, 3 = rain + hail)",
    "Lightning Strike Avg Distance (km)",
    "Lightning Strike Count",
    "Battery (Volts)",
    "Report Interval (Minutes)"
  ];

  const obsObject = {};

  obsData.forEach((value, index) => {
    obsObject[obsLabels[index]] = value;
  });

  const dataContainer = document.getElementById('dataContainer');
  for (const label in obsObject) {
    const labelElement = document.createElement('p');
    labelElement.textContent = `${label}: ${obsObject[label]}`;
    dataContainer.appendChild(labelElement);
  }
}

fetchData();
</script>

</body>
</html>

Note: Make sure the URL specified in the code is the same one as the URL you set up your API on.

If you want to access your API from another device…

If you want to access your API data from another device, you can go to:

[your_device_ip]://:3000/weather

Which looks like:

http://192.168.2.50:3000/weather

or similar.

What is each number in the obs part of the UDP response?

Each number of the obs array of the UDP response correlates to a certain weather stat. Here is the table:

Index Field Units
0 Time Epoch Seconds
1 Wind Lull (minimum 3 second sample) m/s
2 Wind Avg (average over report interval) m/s
3 Wind Gust (maximum 3 second sample) m/s
4 Wind Direction Degrees
5 Wind Sample Interval seconds
6 Station Pressure MB
7 Air Temperature C
8 Relative Humidity %
9 Illuminance Lux
10 UV Index
11 Solar Radiation W/m^2
12 Rain amount over previous minute mm
13 Precipitation Type 0 = none, 1 = rain, 2 = hail, 3 = rain + hail (experimental)
14 Lightning Strike Avg Distance km
15 Lightning Strike Count
16 Battery Volts
17 Report Interval Minutes

You can find more data about the UDP here:
https://weatherflow.github.io/Tempest/api/udp/v171/

Was this helpful?

  • Yes, it was helpful
  • No, it was not helpful
  • No, it lacked detail
  • No, it wasn’t relevant
  • I don’t really care
0 voters

Thanks for your time,
Best_codes