Initial release

This commit is contained in:
Nicolas Leclercq 2015-09-11 16:57:03 +02:00
parent 5fdfa3e127
commit dbb64a439a
2 changed files with 330 additions and 1 deletions

View File

@ -1,2 +1,82 @@
# netatmo-api-go
Simple API to access Netatmo weather station data written in Go
Simple API to access Netatmo weather station data written in Go.
Currently tested only with one weather station, outdoor and indoor modules. Let me know if it works with rain or wind gaude.
## Quickstart
[Create a new netatmo app](https://dev.netatmo.com/dev/createapp)
## Example
```go
import (
"fmt"
"os"
"github.com/exzz/netatmo-api-go"
)
func main() {
n, err := netatmo.NewClient(netatmo.Config{
ClientID: "YOUR_APP_ID",
ClientSecret: "YOUR_APP_SECRET",
Username: "YOUR_CREDENTIAL",
Password: "YOUR_PASSWORD",
})
if err != nil {
fmt.Println(err)
os.Exit(1)
}
dc, err := n.GetDeviceCollection()
if err != nil {
fmt.Println(err)
os.Exit(1)
}
for _, station := range dc.Stations() {
fmt.Printf("Station : %s\n", station.StationName)
for _, module := range station.Modules() {
fmt.Printf("\tModule : %s\n", module.ModuleName)
ts, data := module.Data()
for dataType, value := range data {
fmt.Printf("\t\t%s : %s (%d)\n", dataType, value, ts)
}
}
}
}
```
Output should looks like this :
```
Station : Home
Module : Outside
Temperature : %!s(float32=20.2) (1440302379)
Humidity : %!s(int32=86) (1440302379)
Module : Bedroom 1
CO2 : %!s(int32=500) (1441981664)
Humidity : %!s(int32=69) (1441981664)
Temperature : %!s(float32=21.2) (1441981664)
Module : Bedroom 2
Temperature : %!s(float32=21) (1441981632)
CO2 : %!s(int32=508) (1441981632)
Humidity : %!s(int32=68) (1441981632)
Module : Living room
Temperature : %!s(float32=22.1) (1441981633)
CO2 : %!s(int32=516) (1441981633)
Humidity : %!s(int32=67) (1441981633)
Module : Dining room
Humidity : %!s(int32=75) (1441982895)
Noise : %!s(int32=36) (1441982895)
Pressure : %!s(float32=1015.9) (1441982895)
Temperature : %!s(float32=21.5) (1441982895)
CO2 : %!s(int32=582) (1441982895)
```
## Tips
- Only GetDeviceCollection() method actually do an API call ands refresh all data at once
- Main station is handle as a module, it means that Modules() method returns list of additional modules and station itself.

249
weather.go Normal file
View File

@ -0,0 +1,249 @@
package netatmo
import (
"encoding/json"
"fmt"
"net/http"
"net/http/httputil"
"net/url"
"reflect"
"strings"
"golang.org/x/oauth2"
)
const (
// DefaultBaseURL is netatmo api url
baseURL = "https://api.netatmo.net/"
// DefaultAuthURL is netatmo auth url
authURL = baseURL + "oauth2/token"
// DefaultDeviceURL is netatmo device url
deviceURL = baseURL + "api/devicelist"
)
// Config is used to specify credential to Netatmo API
// ClientID : Client ID from netatmo app registration at http://dev.netatmo.com/dev/listapps
// ClientSecret : Client app secret
// Username : Your netatmo account username
// Password : Your netatmo account password
type Config struct {
ClientID string
ClientSecret string
Username string
Password string
}
// Client use to make request to Netatmo API
// ClientID : Client ID from netatmo app registration at http://dev.netatmo.com/dev/listapps
// ClientSecret : Client app secret
// Username : Your netatmo account username
// Password : Your netatmo account password
// Stations : Contains all Station account
type Client struct {
oauth *oauth2.Config
httpClient *http.Client
httpResponse *http.Response
}
// DeviceCollection hold all devices from netatmo account (stations and modules)
// Error : returned error (nil if OK)
// Stations : List of stations
// Modules : List of additionnal modules
type DeviceCollection struct {
Body struct {
Stations []*Device `json:"devices"`
Modules []*Device
}
}
// Device is a station or a module
// ID : Mac address
// StationName : Station name (only for station)
// ModuleName : Module name
// Type : Module type :
// "NAMain" : for the base station
// "NAModule1" : for the outdoor module
// "NAModule4" : for the additionnal indoor module
// "NAModule3" : for the rain gauge module
// "NAModule2" : for the wind gauge module
// DashboardData : Data collection from device sensors
// DataType : List of available datas
// MainDevice : Id of main station (only for module)
// AssociatedModules : Associated modules (only for station)
type Device struct {
ID string `json:"_id"`
StationName string `json:"station_name"`
ModuleName string `json:"module_name"`
Type string
DashboardData DashboardData `json:"dashboard_data"`
DataType []string `json:"data_type"`
MainDevice string `json:"main_device,omitempty"`
AssociatedModules []*Device `json:"-"`
}
// DashboardData is used to store sensor values
// Temperature : Last temperature measure @ LastMesure (in °C)
// Humidity : Last humidity measured @ LastMesure (in %)
// CO2 : Last Co2 measured @ time_utc (in ppm)
// Noise : Last noise measured @ LastMesure (in db)
// Pressure : Last Sea level pressure measured @ LastMesure (in mb)
// AbsolutePressure : Real measured pressure @ LastMesure (in mb)
// Rain : Last rain measured (in mm)
// Rain1Hour : Amount of rain in last hour
// Rain1Day : Amount of rain today
// WindAngle : Current 5 min average wind direction @ LastMesure (in °)
// WindStrength : Current 5 min average wind speed @ LastMesure (in km/h)
// GustAngle : Direction of the last 5 min highest gust wind @ LastMesure (in °)
// GustStrength : Speed of the last 5 min highest gust wind @ LastMesure (in km/h)
// LastMessage : Contains timestamp of last data received
type DashboardData struct {
Temperature float32 `json:"Temperature,omitempty"`
Humidity int32 `json:"Humidity,omitempty"`
CO2 int32 `json:"CO2,omitempty"`
Noise int32 `json:"Noise,omitempty"`
Pressure float32 `json:"Pressure,omitempty"`
AbsolutePressure float32 `json:"AbsolutePressure,omitempty"`
Rain float32 `json:"Rain,omitempty"`
Rain1Hour float32 `json:"sum_rain_1,omitempty"`
Rain1Day float32 `json:"sum_rain_24,omitempty"`
WindAngle float32 `json:"WindAngle,omitempty"`
WindStrength float32 `json:"WindStrength,omitempty"`
GustAngle float32 `json:"GustAngle,omitempty"`
GustStrengthfloat32 float32 `json:"GustStrengthfloat32,omitempty"`
LastMesure float64 `json:"time_utc"`
}
// NewClient create a handle authentication to Netamo API
func NewClient(config Config) (*Client, error) {
oauth := &oauth2.Config{
ClientID: config.ClientID,
ClientSecret: config.ClientSecret,
Scopes: []string{"read_station"},
Endpoint: oauth2.Endpoint{
AuthURL: baseURL,
TokenURL: authURL,
},
}
token, err := oauth.PasswordCredentialsToken(oauth2.NoContext, config.Username, config.Password)
return &Client{
oauth: oauth,
httpClient: oauth.Client(oauth2.NoContext, token),
}, err
}
// do a url encoded HTTP POST request
func (c *Client) doHTTPPostForm(url string, data url.Values) (*http.Response, error) {
req, err := http.NewRequest("POST", url, strings.NewReader(data.Encode()))
if err != nil {
return nil, err
}
//req.ContentLength = int64(reader.Len())
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
return c.doHTTP(req)
}
// send http GET request
func (c *Client) doHTTPGet(url string, data url.Values) (*http.Response, error) {
if data != nil {
url = url + "?" + data.Encode()
}
req, err := http.NewRequest("GET", url, nil)
if err != nil {
return nil, err
}
return c.doHTTP(req)
}
// do a generic HTTP request
func (c *Client) doHTTP(req *http.Request) (*http.Response, error) {
// debug
debug, _ := httputil.DumpRequestOut(req, true)
fmt.Printf("%s\n\n", debug)
var err error
c.httpResponse, err = c.httpClient.Do(req)
if err != nil {
return nil, err
}
return c.httpResponse, nil
}
// process HTTP response
// Unmarshall received data into holder struct
func processHTTPResponse(resp *http.Response, err error, holder interface{}) error {
defer resp.Body.Close()
if err != nil {
return err
}
// debug
debug, _ := httputil.DumpResponse(resp, true)
fmt.Printf("%s\n\n", debug)
// check http return code
if resp.StatusCode != 200 {
//bytes, _ := ioutil.ReadAll(resp.Body)
return fmt.Errorf("Bad HTTP return code %d", resp.StatusCode)
}
// Unmarshall response into given struct
if err = json.NewDecoder(resp.Body).Decode(holder); err != nil {
return err
}
return nil
}
// GetDeviceCollection returns the list of stations owned by the user, and their modules
func (c *Client) GetDeviceCollection() (*DeviceCollection, error) {
//resp, err := c.doHTTPPostForm(deviceURL, url.Values{"app_type": {"app_station"}})
resp, err := c.doHTTPGet(deviceURL, url.Values{"app_type": {"app_station"}})
dc := &DeviceCollection{}
if err = processHTTPResponse(resp, err, dc); err != nil {
return nil, err
}
// associated each module to its station
for i, station := range dc.Body.Stations {
for _, module := range dc.Body.Modules {
if module.MainDevice == station.ID {
dc.Body.Stations[i].AssociatedModules = append(dc.Body.Stations[i].AssociatedModules, module)
}
}
}
return dc, nil
}
// Stations returns the list of stations
func (dc *DeviceCollection) Stations() []*Device {
return dc.Body.Stations
}
// Modules returns the list of modules associated to this station
// also return station itself in the list
func (s *Device) Modules() []*Device {
modules := s.AssociatedModules
modules = append(modules, s)
return modules
}
// Data returns timestamp and the list of sensor value for this module
func (s *Device) Data() (int, map[string]interface{}) {
m := make(map[string]interface{})
for _, datatype := range s.DataType {
m[datatype] = reflect.Indirect(reflect.ValueOf(s.DashboardData)).FieldByName(datatype).Interface()
}
return int(s.DashboardData.LastMesure), m
}