├── .babelrc ├── .gitignore ├── examples ├── display-recent-data.js └── realtime.js ├── lib └── index.js ├── package.json ├── readme.md └── src └── index.js /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["env"] 3 | } -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | **.DS_Store 5 | **.swp 6 | **.#* 7 | **.*.el 8 | .env 9 | 10 | node_modules 11 | 12 | -------------------------------------------------------------------------------- /examples/display-recent-data.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const AmbientWeatherApi = require('../lib/index') 3 | 4 | const api = new AmbientWeatherApi({ 5 | apiKey: process.env.AMBIENT_WEATHER_API_KEY || 'Put your AW apiKey here', 6 | applicationKey: process.env.AMBIENT_WEATHER_APPLICATION_KEY || 'Put your AW applicationKey here' 7 | }) 8 | 9 | // list the user's devices 10 | api.userDevices() 11 | .then((devices) => { 12 | 13 | devices.forEach((device) => { 14 | // fetch the most recent data 15 | api.deviceData(device.macAddress, { 16 | limit: 5 17 | }) 18 | .then((deviceData) => { 19 | console.log('The 5 most recent temperature reports for ' + device.info.name + ' - ' + device.info.location + ':') 20 | deviceData.forEach((data) => { 21 | console.log(data.date + ' - ' + data.tempf + '°F') 22 | }) 23 | console.log('---') 24 | }) 25 | }) 26 | }) 27 | -------------------------------------------------------------------------------- /examples/realtime.js: -------------------------------------------------------------------------------- 1 | require('dotenv').config() 2 | const AmbientWeatherApi = require('../lib/index') 3 | 4 | // helper function 5 | function getName (device) { 6 | return device.info.name 7 | } 8 | 9 | const apiKey = process.env.AMBIENT_WEATHER_API_KEY || 'Put your AW apiKey here' 10 | const api = new AmbientWeatherApi({ 11 | apiKey, 12 | applicationKey: process.env.AMBIENT_WEATHER_APPLICATION_KEY || 'Put your AW applicationKey here' 13 | }) 14 | 15 | api.connect() 16 | api.on('connect', () => console.log('Connected to Ambient Weather Realtime API!')) 17 | 18 | api.on('subscribed', data => { 19 | console.log('Subscribed to ' + data.devices.length + ' device(s): ') 20 | console.log(data.devices.map(getName).join(', ')) 21 | }) 22 | api.on('data', data => { 23 | console.log(data.date + ' - ' + getName(data.device) + ' current outdoor temperature is: ' + data.tempf + '°F') 24 | }) 25 | api.subscribe(apiKey) 26 | -------------------------------------------------------------------------------- /lib/index.js: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | 3 | var _createClass = function () { function defineProperties(target, props) { for (var i = 0; i < props.length; i++) { var descriptor = props[i]; descriptor.enumerable = descriptor.enumerable || false; descriptor.configurable = true; if ("value" in descriptor) descriptor.writable = true; Object.defineProperty(target, descriptor.key, descriptor); } } return function (Constructor, protoProps, staticProps) { if (protoProps) defineProperties(Constructor.prototype, protoProps); if (staticProps) defineProperties(Constructor, staticProps); return Constructor; }; }(); 4 | 5 | var _requestPromise = require('request-promise'); 6 | 7 | var _requestPromise2 = _interopRequireDefault(_requestPromise); 8 | 9 | var _ramda = require('ramda'); 10 | 11 | var _socket = require('socket.io-client'); 12 | 13 | var _socket2 = _interopRequireDefault(_socket); 14 | 15 | function _interopRequireDefault(obj) { return obj && obj.__esModule ? obj : { default: obj }; } 16 | 17 | function _classCallCheck(instance, Constructor) { if (!(instance instanceof Constructor)) { throw new TypeError("Cannot call a class as a function"); } } 18 | 19 | function _possibleConstructorReturn(self, call) { if (!self) { throw new ReferenceError("this hasn't been initialised - super() hasn't been called"); } return call && (typeof call === "object" || typeof call === "function") ? call : self; } 20 | 21 | function _inherits(subClass, superClass) { if (typeof superClass !== "function" && superClass !== null) { throw new TypeError("Super expression must either be null or a function, not " + typeof superClass); } subClass.prototype = Object.create(superClass && superClass.prototype, { constructor: { value: subClass, enumerable: false, writable: true, configurable: true } }); if (superClass) Object.setPrototypeOf ? Object.setPrototypeOf(subClass, superClass) : subClass.__proto__ = superClass; } 22 | 23 | var EventEmitter = require('events'); 24 | 25 | var API_URL = 'https://api.ambientweather.net/'; 26 | var AW_API_URL = API_URL + 'v1/devices/'; 27 | 28 | module.exports = function (_EventEmitter) { 29 | _inherits(AmbientWeatherApi, _EventEmitter); 30 | 31 | function AmbientWeatherApi(opts) { 32 | _classCallCheck(this, AmbientWeatherApi); 33 | 34 | var apiKey = opts.apiKey, 35 | applicationKey = opts.applicationKey; 36 | 37 | if (!apiKey) { 38 | throw new Error('You need an apiKey'); 39 | } 40 | if (!applicationKey) { 41 | throw new Error('You need an applicationKey'); 42 | } 43 | 44 | var _this = _possibleConstructorReturn(this, (AmbientWeatherApi.__proto__ || Object.getPrototypeOf(AmbientWeatherApi)).call(this)); 45 | 46 | _this.apiKey = apiKey; 47 | _this.applicationKey = applicationKey; 48 | _this.requestQueue = []; 49 | _this.subscribedDevices = []; 50 | return _this; 51 | } 52 | 53 | _createClass(AmbientWeatherApi, [{ 54 | key: '_apiRequest', 55 | value: function _apiRequest(url) { 56 | var _this2 = this; 57 | 58 | return new Promise(function (resolve, reject) { 59 | setTimeout(function () { 60 | (0, _requestPromise2.default)({ 61 | url: url, 62 | json: true 63 | }).then(function (res, body) { 64 | _this2.requestQueue = (0, _ramda.filter)((0, _ramda.pipe)((0, _ramda.equals)(url), _ramda.not), _this2.requestQueue); 65 | resolve(res); 66 | }).catch(function (err) { 67 | // handle rate limiting 68 | if (err.statusCode === 429) { 69 | _this2.requestQueue.push(url); 70 | _this2._apiRequest(url).then(resolve); 71 | } else { 72 | reject(err); 73 | } 74 | }); 75 | }, _this2.requestQueue.length * 1100); 76 | }); 77 | } 78 | }, { 79 | key: '_getUrl', 80 | value: function _getUrl() { 81 | var macAddress = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : ''; 82 | 83 | return AW_API_URL + macAddress + '?apiKey=' + this.apiKey + '&applicationKey=' + this.applicationKey; 84 | } 85 | }, { 86 | key: 'userDevices', 87 | value: function userDevices() { 88 | return this._apiRequest(this._getUrl()); 89 | } 90 | }, { 91 | key: 'deviceData', 92 | value: function deviceData(macAddress, opts) { 93 | if (!macAddress) { 94 | throw new Error('You need a macAddress for deviceData'); 95 | } 96 | var url = this._getUrl(macAddress); 97 | if (opts) { 98 | url += '&' + (0, _ramda.pipe)(_ramda.toPairs, (0, _ramda.map)((0, _ramda.join)('=')), (0, _ramda.join)('&'))(opts); 99 | } 100 | return this._apiRequest(url); 101 | } 102 | }, { 103 | key: 'connect', 104 | value: function connect() { 105 | var _this3 = this; 106 | 107 | if (this.socket) { 108 | return; 109 | } 110 | this.socket = (0, _socket2.default)(API_URL + '?api=1&applicationKey=' + this.applicationKey, { 111 | transports: ['websocket'] 112 | });['error', 'connect'].forEach(function (key) { 113 | _this3.socket.on(key, function (data) { 114 | _this3.emit(key, data); 115 | }); 116 | }); 117 | this.socket.on('subscribed', function (data) { 118 | _this3.subscribedDevices = data.devices || []; 119 | _this3.emit('subscribed', data); 120 | }); 121 | this.socket.on('data', function (data) { 122 | // find the device this data is for using the macAddress 123 | data.device = (0, _ramda.find)((0, _ramda.propEq)('macAddress', data.macAddress), _this3.subscribedDevices); 124 | _this3.emit('data', data); 125 | }); 126 | } 127 | }, { 128 | key: 'disconnect', 129 | value: function disconnect() { 130 | this.socket.disconnect(); 131 | delete this.socket; 132 | } 133 | }, { 134 | key: 'subscribe', 135 | value: function subscribe(apiKeyOrApiKeys) { 136 | var apiKeys = Array.isArray(apiKeyOrApiKeys) ? apiKeyOrApiKeys : [apiKeyOrApiKeys]; 137 | this.socket.emit('subscribe', { apiKeys: apiKeys }); 138 | } 139 | }, { 140 | key: 'unsubscribe', 141 | value: function unsubscribe(apiKeyOrApiKeys) { 142 | var apiKeys = Array.isArray(apiKeyOrApiKeys) ? apiKeyOrApiKeys : [apiKeyOrApiKeys]; 143 | this.socket.emit('unsubscribe', { apiKeys: apiKeys }); 144 | } 145 | }]); 146 | 147 | return AmbientWeatherApi; 148 | }(EventEmitter); -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "ambient-weather-api", 3 | "version": "0.0.6", 4 | "description": "Ambient Weather API", 5 | "main": "lib/index.js", 6 | "repository": { 7 | "type": "git", 8 | "url": "https://github.com/owise1/ambient-weather-api" 9 | }, 10 | "scripts": { 11 | "build": "babel src -d lib", 12 | "test": "echo \"Error: no test specified\" && exit 1" 13 | }, 14 | "keywords": [ 15 | "weather" 16 | ], 17 | "author": "@owise1", 18 | "license": "ISC", 19 | "devDependencies": { 20 | "babel-cli": "^6.26.0", 21 | "babel-preset-env": "^1.6.1", 22 | "dotenv": "^4.0.0" 23 | }, 24 | "dependencies": { 25 | "ramda": "^0.25.0", 26 | "request": "^2.83.0", 27 | "request-promise": "^4.2.2", 28 | "socket.io-client": "^2.0.4" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # Ambient Weather API 2 | 3 | A simple wrapper for the *forthcoming* AmbientWeather.net API 4 | 5 | * [API Docs](https://ambientweather.docs.apiary.io/) 6 | * [API Wiki](https://github.com/ambient-weather/api-docs/wiki) 7 | * check out the [examples](examples/) 8 | 9 | ## Installation 10 | ``` 11 | npm install ambient-weather-api 12 | ``` 13 | 14 | ## Getting Started 15 | 16 | ``` 17 | const api = new AmbientWeatherApi({ 18 | apiKey: 'Put your AW apiKey here', 19 | applicationKey: 'Put your AW applicationKey here' 20 | }) 21 | ``` 22 | 23 | ## REST Methods 24 | 25 | * `userDevices()` - list the user's devices 26 | * `@return` - Promise containing array of device objects 27 | * `deviceData(macAddress, options)` - fetch data for a specific device 28 | * `macAddress` - (required) 29 | * `options` - limit, endDate see [docs](https://ambientweather.docs.apiary.io/) 30 | * `@return` - Promise containing array of data objects 31 | 32 | ## Realtime Methods 33 | 34 | * `connect` - connect to the realtime API 35 | * `disconnect` - disconnect from the realtime API 36 | * `subscribe` - `apiKeys` - (required) can be a `string` of a single `apiKey` or an `array` of multiple `apiKey`s. Will listen for data on all the devices for all of the user's `apiKeys`s. See `Event: subscribed` & `Event: data` 37 | * `unsubscribe` - `apiKeys` - (required) can be a `string` of a single `apiKey` or an `array` of multiple `apiKey`s. Will stop listening for data on all of the user's `apiKey`s devices. See `Event: subscribed` 38 | * `Event: subscribed` - emitted when successfully subscribed to one or more `apiKeys` using the `subscribe` method. This event is also emitted after sucessfully unsubscribing. It will list all the currently subscribed devices 39 | * `data.devices` - array of device objects currently subscribed to 40 | * `Event: data` - emitted on new data for a subscribed device 41 | * `data` - the weather data point 42 | * `data.device` - the device that data point is for 43 | 44 | 45 | -------------------------------------------------------------------------------- /src/index.js: -------------------------------------------------------------------------------- 1 | import request from 'request-promise' 2 | import { propEq, find, equals, filter, not, map, pipe, toPairs, join } from 'ramda' 3 | import io from 'socket.io-client' 4 | const EventEmitter = require('events') 5 | 6 | const API_URL = 'https://api.ambientweather.net/' 7 | const AW_API_URL = API_URL + 'v1/devices/' 8 | 9 | module.exports = class AmbientWeatherApi extends EventEmitter { 10 | constructor (opts) { 11 | const { apiKey, applicationKey } = opts 12 | if (!apiKey) { 13 | throw new Error('You need an apiKey') 14 | } 15 | if (!applicationKey) { 16 | throw new Error('You need an applicationKey') 17 | } 18 | super() 19 | this.apiKey = apiKey 20 | this.applicationKey = applicationKey 21 | this.requestQueue = [] 22 | this.subscribedDevices = [] 23 | } 24 | 25 | _apiRequest (url) { 26 | return new Promise((resolve, reject) => { 27 | setTimeout(() => { 28 | request({ 29 | url, 30 | json: true 31 | }) 32 | .then((res, body) => { 33 | this.requestQueue = filter(pipe(equals(url), not), this.requestQueue) 34 | resolve(res) 35 | }) 36 | .catch((err) => { 37 | // handle rate limiting 38 | if (err.statusCode === 429) { 39 | this.requestQueue.push(url) 40 | this._apiRequest(url).then(resolve) 41 | 42 | } else { 43 | reject(err) 44 | } 45 | }) 46 | }, this.requestQueue.length * 1100) 47 | }) 48 | } 49 | 50 | _getUrl (macAddress = '') { 51 | return AW_API_URL + macAddress + '?apiKey=' + this.apiKey + '&applicationKey=' + this.applicationKey 52 | } 53 | 54 | userDevices () { 55 | return this._apiRequest(this._getUrl()) 56 | } 57 | 58 | deviceData (macAddress, opts) { 59 | if (!macAddress) { 60 | throw new Error('You need a macAddress for deviceData') 61 | } 62 | let url = this._getUrl(macAddress) 63 | if (opts) { 64 | url += '&' + pipe( 65 | toPairs, 66 | map(join('=')), 67 | join('&') 68 | )(opts) 69 | } 70 | return this._apiRequest(url) 71 | } 72 | 73 | connect () { 74 | if (this.socket) { 75 | return 76 | } 77 | this.socket = io(API_URL + '?api=1&applicationKey=' + this.applicationKey, { 78 | transports: ['websocket'] 79 | }) 80 | ;['error', 'connect'].forEach((key) => { 81 | this.socket.on(key, (data) => { 82 | this.emit(key, data) 83 | }) 84 | }) 85 | this.socket.on('subscribed', (data) => { 86 | this.subscribedDevices = data.devices || [] 87 | this.emit('subscribed', data) 88 | }) 89 | this.socket.on('data', (data) => { 90 | // find the device this data is for using the macAddress 91 | data.device = find(propEq('macAddress', data.macAddress), this.subscribedDevices) 92 | this.emit('data', data) 93 | }) 94 | } 95 | disconnect () { 96 | this.socket.disconnect() 97 | delete this.socket 98 | } 99 | subscribe (apiKeyOrApiKeys) { 100 | const apiKeys = Array.isArray(apiKeyOrApiKeys) ? apiKeyOrApiKeys : [apiKeyOrApiKeys] 101 | this.socket.emit('subscribe', { apiKeys }) 102 | } 103 | unsubscribe (apiKeyOrApiKeys) { 104 | const apiKeys = Array.isArray(apiKeyOrApiKeys) ? apiKeyOrApiKeys : [apiKeyOrApiKeys] 105 | this.socket.emit('unsubscribe', { apiKeys }) 106 | } 107 | } 108 | --------------------------------------------------------------------------------