Thread Rating:
  • 0 Vote(s) - 0 Average
  • 1
  • 2
  • 3
  • 4
  • 5
Netatmo Weather Station
#1
Netatmo Weather Station consists of a set of wireless modules that include
  • 1 "Main" station. Indoor measurements of temperature, humidity, pressure, CO2, and noise.
  • 1 "Outdoor" module, measuring temperature and humidity.
  • From 0 to 3 "Indoor" modules, measuring temperature, humidity, and CO2.
  • 1 optional "Rain" module, measuring rainfall.
  • 1 optional "Wind" module, measuring wind strength and angle, and gust strength and angle.
(Netatmo also makes thermostat and camera units. This driver does not work with them.)

If you have more than one Main module, you can add additional instances of the driver, and identify each Main module by its device ID.

Netatmo does not provide direct access to read information from the modules. All communication goes through the Netatmo web servers. Each Main station sends measurements to the web servers (at a ten minute interval), and the Netatmo Weather Station CQC driver requests measurement data from the web servers.

This driver configures itself from data on the Netatmo servers. If create fields only for modules which are present in your setup. It also reads the preferences from your settings on the Netatmo servers, which you configure using the Netatmo App or Web App.

New measurements are available at ten minute intervals. The driver stores a table of measurements from the previous 24 hours. A "backdoor" method provides access to the historical data. Also, on startup, the driver queries the Netatmo site for 24 hours of historical data. The Netatmo developer API provides methods for querying historical data over longer time periods (days, weeks). You can get the access_token to query directly from the Netatmo servers using the backdoor method of this driver.

Some field names have the suffix "_24h", such as "MinTemperature_24h", which indicates the reading is for the last 24 hour period. This is because the data from the Netatmo site resets min and max values at midnight, so "MinTemperature" is the minimum temperature recorded since midnight, but "MinTemperature_24h" is the minimum temperature in the last 24 hours.

Feb 16, 2017

I have attached a new version of the Netatmo Weather Station driver package.

It should fix the occasional driver disconnects that were occurring.

Major new addition: 

Netatmo has added to their API a new function: ability to query for data from Netatmo weather stations from any geographical region.

I have enhanced the driver to allow you to specify the size of a region around you. Your latitude and longitude are taken from your CQC setup, and the driver configuration lets you choose the size of a region around you (a square, essentially). The driver will average the readings from the neighboring Netatmo Weather Stations, and store them for a pseudo-module (called "Region").

If you do not have a wind or rain gauge, but people in your region do have them, then you will get the readings from their modules.

Feb 22 2017

The driver was still occasionally losing connection. I tracked it down to some HTTP GET calls taking a long time, and hitting the timeout.

I have updated the driver to use the asynchronous HTTP class for the two main data requests.

December 31 2017

Updated to check bounds for latitude and longitude against Netatmo's range of -85 < latitude < 85  and -180 < longitude < 180.

Changed all LogMsg3 calls to LogMsg2, since LogMsg3 is broken and does not substitute values for the %(1) %(2) %(3) tokens.


Attached Files
.cqcdrvpack   Netatmo Weather Station Dev.CQCDrvPack (Size: 24.03 KB / Downloads: 4)
Reply
#2
Set Up the Modules and Apps

First, make sure the modules are installed and working using the Android or iOS apps (or web app at https://my.netatmo.com/app/station) that Netatmo provides. You will have to register at my.netatmo.com, so you should end up with a login ID using an email address and password.

Use the apps the set your preferences for metric/imperial units, mph/mph/knots, etc.

Whenever you change your preferences, you must "reconfigure" the Netatmo Weather Station driver before it will know the new preferences.

Register as a Netatmo Developer

Go to https://dev.netatmo.com and sign up to be a Netatmo developer. It is a free service.

Create an App. You can call it anything you want, but "CQC" is a good choice.

Once the App is created, you should have a "Client id" and a "Client secret".

Configure the Driver

Now you are ready to add and configure the Netatmo Weather Station driver.

Fill in the login name and password, and the client credentials. If you have only one
Netatmo Main module, you can leave the "Device ID" field empty. If you have more than
one Main module, you will add one Netatmo Weather Station driver for each Main module,
and you must enter a device id for each driver.
Reply
#3
The macro file CMLBin.Netatmo.Query allows web pages to query data from the Netatmo Weather Station driver. This can be used to draw graphs from the data. An example web page is provided in another post.

The query is in the format:

Code:
http://IPofCQCWebServer/CMLBin/User/Netatmo/Query?
    time=12345&
    moniker=netatmo_moniker&
    request=encoded_params
  • "time" is the current time, to prevent the web server from returning a cached result.
  • "moniker" is the moniker of the netatmo driver.
  • "request" is the actual request, encoded using "encodeURIComponent" javascript function.

Look at the sample javascript for chart drawing (in a later post) for an example.

The result is a string in JSON format.

Valid requests and result examples:

Request:
Code:
Sample?sample=latest
Sample?sample=all

Result (if "latest", the value arrays are length 1):
Code:
{ "body": {
    "unit": "0",
    "windunit": "1",
    "pressureunit": "0",
    "feel_like_algo": "1",
    "time_utc": [
        "1456081957",
        "1456082564"
        ...
    ],
    "modules": [
        {
            "_id": "00:ee:00:50:01:00:00",
            "module_name": "LivingRoom",
            "type": "NAMain",
            "type_name": "Main",
            "Humidity": ["65","64","64",...],
            "Noise": ["44","44","45",...],
            "Pressure": ["1021.60","1021.50","1021.30",...],
            "Temperature": ["18.2","18.4","18.7",...]
        },
        {
            "_id": "00:ee:00:50:01:00:00",
            "module_name": "Outdoor",
            "type": "NAModule1",
            "type_name": "Outdoor",
            "Humidity": ["65","64","64",...],
            ...
        }
    ]
}

Request:
Code:
AccessToken?

Result:
Code:
{ "body": { "access_token": "12243429489484", "device_id": "00:ee:00:50:01:00:00" } }

When you have the access_token and device_id, you can send requests to the Netatmo API, as described at https://dev.netatmo.com/doc/

Because you have an access_token, you do not need to go through authentication - the driver has already done it. Note that access_token expires, so you should ask the driver for the access_token each time before you make an API request.

If an error occurs, the result is:
Code:
{ "error": "error information" }
Reply
#4
The driver implements QueryTextVal with two requests. These are intended for use with CMLBin.Netatmo.Query
Code:
Method QueryTextVal(
    [In] String iValId,
    [In] String iParams,
    [Out] String oToFill) Returns Boolean
  • Values for iValId: "Sample", "AccessToken"
  • Parameters for "Sample": "sample=latest", "sample=all".
  • Parameters for "AccessToken".
Return value (in oToFill) is a JSON string, as described above for CMLBin.Netatmo.Query.
Reply
#5
This html/javascript sample shows how to draw a 24 hour chart of humidity and temperature for all modules.

It uses the google chart api to draw a dual-axis line chart. Google charts is very powerful and flexible, and this sample is just a starting point. You can dive in and customize the chart as much as you want.

To use it:
  • Put a file containing this code somewhere under HTMLRoot (eg. /HTMLRoot/Netatmo/chart_sample.html).
  • Create a interface using the Interface Template Editor.
  • Add a Web Browser widget. Make it fairly large, like 900x700.
  • For the Initial URL, use the path to chart_sample.html. Do not Auto Refresh.
Reply
#6
Code is split into two posts.

Code:
<!DOCTYPE html>
<meta http-equiv="X-UA-Compatible" content="IE=EDGE" />
<html>
  <head>
    <script type="text/javascript" src="https://www.gstatic.com/charts/loader.js"></script>
    <script type="text/javascript">

      //- Configuration for your CQC system -------------

      //  Set the CQC driver Moniker here:
      var moniker = "Netatmo";

      //  Address of the machine and port where CQC web server is:
      var webServerAddress = "192.168.49.160:8080";

      //  Chart options reference available at:
      //  https://developers.google.com/chart/interactive/docs/gallery/linechart
      var options = {
        curveType: 'function',

        legend: {
          position: 'bottom',
          textStyle: {
            color: 'white'
          }
        },
        backgroundColor: 'black',
        chartArea: {
          left: '15%', right: '15%', top: '5%', bottom: '15%'
        },
        vAxes: {
          0: {  // Humidity
                minValue: 0, maxValue: 100, min: 0, max: 100,
                viewWindow: { min: 0, max: 100 },
                textStyle: { color: 'white' }
             },
          1: {  // Temperature
                viewWindow: { min: 0, max: 30 },
                gridlines: { count: 5 },
                //ticks: { [15, 20] },
                textStyle: { color: 'white' }
             }
        },
        series: {
          //  Filled in by DrawChart.
        },
        hAxis: {
          textStyle: { color: 'white' }
        },
        animation: { duration: 3000 }
      };


      //- End of configuration --------------------------
Reply
#7
Code:
//  The number of entries to remove when decimating the dataTable.
      //  The dataTable is decimated to smooth the lines in the chart.
      //  Value of 2 means: of every three rows, delete two. This will
      //  change the data interval from 10 minutes to 30 minutes.
      var decimateRemove = 2;

      google.charts.load('current', {'packages':['corechart']});

      google.charts.setOnLoadCallback(drawChart);
      var URL = "http://" + webServerAddress + "/CMLBin/User/Netatmo/Query?";

      var leftColor = 0x3174D9;
      var leftColorStep = 0x003000;
      var rightColor = 0xD7D931;
      var rightColorStep = -0x003000;

      var dataTable;
      var chart;
      var interval;
      var moduleIDs = [];
      var xmlHttp = new XMLHttpRequest();

      var tempUnits = 0;
      var windUnits = 0;
      var pressureUnits = 0;
      var humidityCols = 0;

      function convertTempUnits(inTemp) {
        if (tempUnits == 1) {
          return ((inTemp * 9.0) / 5.0) + 32.0;
        }
        return inTemp;
      }

      function convertWindUnits(inStrength) {
        if (windUnits == 1) {
          return inStrength * 0.621371;

        } else if (windUnits == 2) {
          return inStrength * 0.277778;

        } else if (windUnits == 3) {
          if (inStrength < 2) {
            return 0;
          } else if (inStrength < 6) {
            return 1;
          } else if (inStrength < 13) {
            return 2;
          } else if (inStrength < 21) {
            return 3;
          } else if (inStrength < 31) {
            return 4;
          } else if (inStrength < 41) {
            return 5;
          } else if (inStrength < 51) {
            return 6;
          } else if (inStrength < 62) {
            return 7;
          } else if (inStrength < 75) {
            return 8;
          } else if (inStrength < 90) {
            return 9;
          } else if (inStrength < 104) {
            return 10;
          } else if (inStrength < 120) {
            return 11;
          }
          return 12
        } else if (windUnits == 4) {
          return inStrength * 0.539957;
        }
        return inStrength;
      }

      function convertPressureUnits(inPressure) {
        if (pressureUnits == 1) {
          return inPressure * 0.750061578;
        } else if (pressureUnits == 2) {

          return inPressure * 0.02952998;
        }
        return inPressure;
      }


      function setMinMax() {
        var minHum = 2000.0;
        var maxHum = -2000.0;
        var minTemp = 2000.0;
        var maxTemp = -2000.0;
        var i, j, t;
        var rows = dataTable.getNumberOfRows();
        var cols = dataTable.getNumberOfColumns();

        for (i = 0; i < rows; i++) {
          for (j = 1; j < cols; j++) {
            t = dataTable.getValue(i, j);
            if (j <= humidityCols) {
              if (t < minHum) { minHum = t; }
              if (t > maxHum) { maxHum = t; }
            } else {
              if (t < minTemp) { minTemp = t; }
              if (t > maxTemp) { maxTemp = t; }
            }
          }
        }

        var minAxis = (Math.floor(minTemp / 5)) * 5;
        var maxAxis = (Math.ceil (maxTemp / 5)) * 5;
        options.vAxes["1"].viewWindow.minValue = minAxis;
        options.vAxes["1"].viewWindow.maxValue = maxAxis;
        options.vAxes["1"].viewWindow.min = minAxis;
        options.vAxes["1"].viewWindow.max = maxAxis;
      }

      function formURL(request, params) {
        return URL
            + "&time=" + (new Date()).getTime()
            + "&moniker=" + moniker
            + "&" + request + "=" + encodeURIComponent(params);
      }

      function queryURL(request, params) {
        xmlHttp.open( "GET", formURL(request, params), false );
        xmlHttp.send( null );
        return xmlHttp.responseText;
      }

      function decimateData(src, numToRemove) {
        var t = src.clone();
        var i = t.getNumberOfRows() - 2 - numToRemove;
        while (i > 1) {
          t.removeRows(i, numToRemove);
          i -= (numToRemove + 1);
        }
        return t;
      }

      function sampleToRow(body) {
        var modules = body.modules;
        var modIndex;

        var row = [ new Date(parseInt(body.time_utc[0]) * 1000) ];

        for (modIndex = 0; modIndex < modules.length; modIndex++) {
          if ("Humidity" in modules[modIndex]) {
            row.push(parseFloat(modules[modIndex].Humidity));
          }
        }
        for (modIndex = 0; modIndex < modules.length; modIndex++) {
          if ("Temperature" in modules[modIndex]) {
            row.push(convertTempUnits(parseFloat(modules[modIndex].Temperature)));
          }
        }
        return row;
      }

      function updateChart() {
        var params = "sample=latest";

        var responseText = queryURL("Sample", params);
        var result = JSON.parse(responseText);

        if ("error" in result) {
          document.getElementById("chart").innerHTML = responseText;
        } else {
          var rowTmp = sampleToRow(result.body);
          var toCmp = dataTable.getValue(dataTable.getNumberOfRows()-1, 0).getTime();

          //  If the timestamp of the latest graph sample is different
          //  from the timestamp of the last item in the dataTable, then
          //  it is time to update the dataTable and redraw.
          if (rowTmp[0].getTime() != toCmp) {
            dataTable.removeRow(0);
            dataTable.addRow(rowTmp);
            setMinMax();
            chart.draw(decimateData(dataTable, decimateRemove), options);
          }
        }
      }

      function drawChart() {
        dataTable = new google.visualization.DataTable();

        //  Get the list of stations and modules
        var responseText = queryURL("Sample", "sample=all");
        var result = JSON.parse(responseText);

        if ("error" in result) {
          document.getElementById('chart').innerHTML = responseText;
          return;
        }

        var body = result.body;
        var modIndex;
        var numColumns = 0;

        tempUnits = body.unit;
        windUnits = body.windunit;
        pressureUnits = body.pressureunit;

        var modules = body.modules;

        //  Fill the entire dataTable.
        dataTable.addColumn("datetime", "Time");
        var color = leftColor;
        options.vAxes["0"].textStyle.color = '#' + color.toString(16);
        for (modIndex = 0; modIndex < modules.length; modIndex++) {
          if ("Humidity" in modules[modIndex]) {
            dataTable.addColumn("number", modules[modIndex].module_name + "(H)");
            options.series[(numColumns++).toString()] =
              { targetAxisIndex: 0, color: '#'+color.toString(16) };
            color += leftColorStep
          }
        }
        humidityCols = numColumns;
        color = rightColor;
        options.vAxes["1"].textStyle.color = '#' + color.toString(16);
        for (modIndex = 0; modIndex < modules.length; modIndex++) {
          if ("Temperature" in modules[modIndex]) {
            dataTable.addColumn("number", modules[modIndex].module_name + "(T)");
            options.series[(numColumns++).toString()] =
              { targetAxisIndex: 1, color: '#'+color.toString(16) };
            color += rightColorStep;
          }
        }

        var time_utc = body.time_utc;
        for (var i = 0; i < time_utc.length; i++) {
          var row = [ new Date(parseInt(body.time_utc[i]) * 1000) ];
          for (modIndex = 0; modIndex < modules.length; modIndex++) {
            var module = modules[modIndex];
            if ("Humidity" in module) {
              row.push(parseFloat(module.Humidity[i]));
            }
          }
          for (modIndex = 0; modIndex < modules.length; modIndex++) {
            var module = modules[modIndex];
            if ("Temperature" in module) {
              row.push(convertTempUnits(parseFloat(module.Temperature[i])));
            }
          }
          dataTable.addRow(row);
        }

        setMinMax();


       chart = new google.visualization.LineChart(document.getElementById("chart"));

        chart.draw(decimateData(dataTable, decimateRemove), options);


        //  Check for update every minute.
        interval = setInterval(updateChart, 60000);

      }

    </script>
  </head>
  <body>
    <div id="chart" style="height: 700px"></div>
  </body>
</html>
Reply
#8
Wow, that's pretty elaborate. It might be a while before someone else can try it I guess, unless someone else just happens to have one of those guys already.
Dean Roddey
Software Geek Extraordinaire
Reply
#9
This is gold. I am trying to unload my Bloomsky for a Netatmo, but I might just bite the bullet and grab the starter pack to get going.
do the needful ...
Hue | Sonos | Harmony | Elk M1G // Netatmo / Brultech
Reply
#10
(01-03-2017, 03:42 PM)jkmonroe Wrote: This is gold.  I am trying to unload my Bloomsky for a Netatmo, but I might just bite the bullet and grab the starter pack to get going.

Why is it gold?  Been running Virtual Weather Station (VWS) software with a Davis Instruments WS for 8+ years now. VWS handles all of the data for me, publishes to Weather Underground, NWS, sites, etc.  Running a software serial port splitter to feed both VWS and CQC the data from the Davis.
Reply


Possibly Related Threads...
Thread Author Replies Views Last Post
  Weather driver - solar output Ron Haley 15 4,699 06-13-2015, 07:15 AM
Last Post: Dean Roddey
  NWS Weather Alert Driver beelzerob 149 35,871 05-12-2013, 04:42 PM
Last Post: sic0048
  Davis Vantage Pro Weather Station Driver Mikla 42 16,544 09-24-2012, 09:39 AM
Last Post: Wayneb123
  wunderground weather station driver Sendero 10 5,337 02-09-2011, 05:23 AM
Last Post: khill
  NASA Weather Image Driver Mikla 7 3,553 08-06-2008, 07:02 AM
Last Post: jkish

Forum Jump:


Users browsing this thread: 1 Guest(s)