├── setup.cfg ├── .gitignore ├── example_keys.py ├── setup.py ├── examples ├── get_last_live_sample.py ├── wolfram_drop.py └── get_historical.py ├── tests ├── user_info_test.py ├── auth_test.py ├── local_test.py ├── appliances_test.py └── samples_test.py ├── CHANGELOG.md ├── README.md ├── LICENSE.txt └── neurio └── __init__.py /setup.cfg: -------------------------------------------------------------------------------- 1 | [metadata] 2 | description-file = README.md 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | tests/test_keys.py 2 | *.py? 3 | *.egg-info 4 | *.swp 5 | MANIFEST 6 | build/ 7 | dist/ 8 | -------------------------------------------------------------------------------- /example_keys.py: -------------------------------------------------------------------------------- 1 | key = "deadb33fdeadb33fdeadb3" 2 | secret = "b3deadb33fdeadb33fdead" 3 | sensor_id = "0x0000AABBCCDDEEFF" 4 | location_id = "AABB_CCDDEEFF001122334" 5 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import sys 2 | sys.path.append('.') 3 | 4 | try: 5 | from setuptools import setup 6 | except ImportError: 7 | from distutils.core import setup 8 | 9 | setup( 10 | name = 'neurio', 11 | packages = ['neurio'], 12 | version = "0.3.1", 13 | description = 'Neurio energy sensor and appliance automation API library', 14 | author = 'Jordan Husney', 15 | author_email = 'jordan.husney@gmail.com', 16 | url = 'https://github.com/jordanh/neurio-python', 17 | download_url = 'https://github.com/jordanh/neurio-python/tarball/0.3.0', 18 | keywords = ['neurio', 'iot', 'energy', 'sensor', 'smarthome', 'automation'], 19 | classifiers = [], 20 | install_requires = ['requests'], 21 | ) 22 | -------------------------------------------------------------------------------- /examples/get_last_live_sample.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | import sys 18 | sys.path.append("..") 19 | 20 | import neurio 21 | 22 | import example_keys 23 | sensor_id = "0x0013A20040B65FAD" 24 | 25 | tp = neurio.TokenProvider(key=example_keys.key, secret=example_keys.secret) 26 | nc = neurio.Client(token_provider=tp) 27 | 28 | sample = nc.get_samples_live_last(sensor_id=sensor_id) 29 | 30 | print "Current power consumption: %d W" % (sample['consumptionPower']) 31 | -------------------------------------------------------------------------------- /tests/user_info_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import sys 19 | sys.path.append(".") 20 | sys.path.append("..") 21 | 22 | import neurio 23 | import test_keys 24 | 25 | import unittest 26 | from datetime import datetime, timedelta 27 | 28 | class UserInfoTest(unittest.TestCase): 29 | def setUp(self): 30 | self.tp = neurio.TokenProvider(key=test_keys.key, 31 | secret=test_keys.secret) 32 | self.nc = neurio.Client(token_provider=self.tp) 33 | 34 | def user_info_test(self): 35 | user_info = self.nc.get_user_information() 36 | self.assertIsInstance(user_info, dict) 37 | self.assertEqual(user_info["status"], "active") 38 | 39 | 40 | if __name__ == '__main__': 41 | unittest.main() 42 | -------------------------------------------------------------------------------- /examples/wolfram_drop.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | import sys 18 | sys.path.append("..") 19 | 20 | import neurio 21 | import example_keys 22 | sensor_id = "0x0013A20040B65FAD" 23 | 24 | import requests 25 | databin_id = "abcd_123" 26 | 27 | tp = neurio.TokenProvider(key=example_keys.key, secret=example_keys.secret) 28 | nc = neurio.Client(token_provider=tp) 29 | 30 | # Get the last 90-seconds of activity: 31 | samples = nc.get_samples_live(sensor_id=sensor_id, last=90) 32 | 33 | # Put in 15-second resolution data into the Wolfram data drop: 34 | for sample in samples[::15]: 35 | print sample 36 | payload = {} 37 | for k in ("timestamp", "consumptionPower", "consumptionEnergy"): 38 | payload[k] = sample[k] 39 | payload["bin"] = databin_id 40 | r = requests.get("https://datadrop.wolframcloud.com/api/v1.0/Add", params=payload) 41 | print r 42 | -------------------------------------------------------------------------------- /examples/get_historical.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import sys 19 | import pprint 20 | sys.path.append("..") 21 | from datetime import datetime, timedelta 22 | 23 | import neurio 24 | import example_keys 25 | sensor_id = "0x0013A20040B65FAD" 26 | 27 | 28 | # Authenticate 29 | tp = neurio.TokenProvider(key=example_keys.key, secret=example_keys.secret) 30 | nc = neurio.Client(token_provider=tp) 31 | 32 | # Calculate 15 minutes ago, format as ISO timestamp: 33 | print " now = %s" % ( 34 | datetime.utcnow().replace(microsecond=0).isoformat()) 35 | fifteen_min_ago = datetime.utcnow() - timedelta(minutes=15) 36 | fifteen_min_ago = fifteen_min_ago.replace(microsecond=0).isoformat() 37 | print "fifteen min ago = %s" % (fifteen_min_ago) 38 | 39 | print "Samples:" 40 | samples = nc.get_samples(sensor_id=sensor_id, start=fifteen_min_ago, 41 | granularity="minutes", frequency=5) 42 | print "samples =" 43 | pprint.pprint(samples) 44 | 45 | print "Stats Samples:" 46 | samples = nc.get_samples_stats(sensor_id=sensor_id, start=fifteen_min_ago, 47 | granularity="minutes", frequency=5) 48 | print "samples =" 49 | pprint.pprint(samples) 50 | -------------------------------------------------------------------------------- /tests/auth_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import sys 19 | sys.path.append(".") 20 | sys.path.append("..") 21 | 22 | import neurio 23 | import test_keys 24 | import example_keys 25 | 26 | import unittest 27 | 28 | class AuthTest(unittest.TestCase): 29 | def test_token_provider_init(self): 30 | tp = neurio.TokenProvider(key=test_keys.key, 31 | secret=test_keys.secret) 32 | self.assertIsNotNone(tp) 33 | 34 | def test_token_provider_get_token(self): 35 | tp = neurio.TokenProvider(key=test_keys.key, 36 | secret=test_keys.secret) 37 | self.assertIsNotNone(tp.get_token(), "unable to fetch token") 38 | 39 | def test_token_provider_invalid_credentials(self): 40 | tp = neurio.TokenProvider(key=example_keys.key, 41 | secret=example_keys.secret) 42 | with self.assertRaises(Exception): 43 | tp.get_token() 44 | 45 | def test_token_provider_none(self): 46 | with self.assertRaises(ValueError): 47 | neurio.Client(token_provider=None) 48 | 49 | def test_token_provider_type(self): 50 | with self.assertRaises(ValueError): 51 | neurio.Client(token_provider=object()) 52 | 53 | 54 | if __name__ == '__main__': 55 | unittest.main() 56 | -------------------------------------------------------------------------------- /tests/local_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import sys 19 | sys.path.append(".") 20 | sys.path.append("..") 21 | 22 | import neurio 23 | import test_keys 24 | 25 | import unittest 26 | from datetime import datetime, timedelta 27 | import json 28 | 29 | class UserInfoTest(unittest.TestCase): 30 | def setUp(self): 31 | self.tp = neurio.TokenProvider(key=test_keys.key, 32 | secret=test_keys.secret) 33 | self.nc = neurio.Client(token_provider=self.tp) 34 | 35 | def test_local_current_sample(self): 36 | user_info = self.nc.get_user_information() 37 | ips = [ 38 | sensor['ipAddress'] 39 | for sublist in [ 40 | location['sensors'] for location in user_info['locations'] 41 | ] 42 | for sensor in sublist if sensor['sensorType'] == 'neurio' 43 | ] 44 | self.assertGreater(len(ips), 0) 45 | # test static method: 46 | sample = neurio.Client.get_local_current_sample(ips[0]) 47 | self.assertGreater(len(sample['timestamp']), 0) 48 | 49 | def test_local_current_sample_ip_arg(self): 50 | bad_ip1 = "hostname.domain" 51 | with self.assertRaises(ValueError): 52 | neurio.Client.get_local_current_sample(bad_ip1) 53 | bad_ip2 = "255.256.257.258" 54 | with self.assertRaises(ValueError): 55 | neurio.Client.get_local_current_sample(bad_ip2) 56 | 57 | 58 | if __name__ == '__main__': 59 | unittest.main() 60 | -------------------------------------------------------------------------------- /tests/appliances_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import sys 19 | sys.path.append(".") 20 | sys.path.append("..") 21 | 22 | import neurio 23 | import test_keys 24 | 25 | import unittest 26 | from datetime import datetime, timedelta 27 | 28 | class AppliancesTest(unittest.TestCase): 29 | def setUp(self): 30 | self.tp = neurio.TokenProvider(key=test_keys.key, 31 | secret=test_keys.secret) 32 | self.nc = neurio.Client(token_provider=self.tp) 33 | 34 | def test_get_appliances(self): 35 | apps = self.nc.get_appliances(location_id=test_keys.location_id) 36 | self.assertIsInstance(apps, list) 37 | self.assertGreater(len(apps), 0, "no appliance information received") 38 | 39 | def test_get_appliance(self): 40 | apps = self.nc.get_appliances(location_id=test_keys.location_id) 41 | self.assertIsInstance(apps, list) 42 | self.assertGreater(len(apps), 0, "no appliance information received") 43 | try: 44 | stringtype = basestring 45 | except NameError: 46 | stringtype = str 47 | self.assertIsInstance(apps[0]["id"], stringtype) 48 | app = self.nc.get_appliance(apps[0]["id"]) 49 | self.assertIsInstance(app, dict) 50 | self.assertEqual(apps[0]["id"], app["id"]) 51 | 52 | def test_get_appliance_event_by_location(self): 53 | pass 54 | 55 | def test_get_appliance_event_after_time(self): 56 | pass 57 | 58 | def test_get_appliance_event_by_appliance(self): 59 | pass 60 | 61 | def test_get_appliance_stats_by_location(self): 62 | pass 63 | 64 | def test_get_appliance_stats_by_appliance(self): 65 | pass 66 | 67 | if __name__ == '__main__': 68 | unittest.main() 69 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change Log 2 | All notable changes to this project will be documented in this file. 3 | This project adheres to [Semantic Versioning](http://semver.org/). 4 | 5 | ## [0.3.1] 6 | ### Changes 7 | - Made `get_local_current_sample()` method static. 8 | 9 | ## [0.3.0] 10 | ### Added 11 | - Added method get_local_current_sample for querying sensor on local network 12 | - Added additional unit tests 13 | 14 | ### Changes 15 | - Alphabetized structure of methods in `__init__.py` 16 | 17 | ## [0.2.10] 18 | ### Added 19 | - Added Change Log 20 | 21 | ### Changes 22 | - Fixes for Python 3 compatibility 23 | 24 | ## [0.2.9] - 2016-01-01 25 | ### Changes 26 | - Bumped version number for proper distribution 27 | 28 | ## [0.2.8] - 2016-01-01 29 | ### Added 30 | - Added `get_appliance` method 31 | 32 | ### Changes 33 | - Moved test credentials to its own file. 34 | - Added unit tests for appliance methods 35 | - Updated documentation on how to retrieve your Neurio API key 36 | - Made all source code plain ol' ASCII 37 | - Copyright updates 38 | 39 | ## [0.2.7] - 2015-03-26 40 | ### Changes 41 | - Switched from Neurio's staging to production servers, that explains 42 | why the library wasn't working for some! 43 | 44 | ## [0.2.6] - 2015-03-24 45 | ### Added 46 | - `example_keys.py` 47 | 48 | ## [0.2.4] - 2015-03-22 49 | ### Changes 50 | - Changed back to install_requires 51 | - Simplified where version information is stored 52 | 53 | ### Removed 54 | - Removed `neurio/_version.py` 55 | 56 | ## [0.2.3] - 2015-03-22 57 | ### Changes 58 | - Using setup_requires again 59 | 60 | ## [0.2.2] - 2015-03-22 61 | ### Added 62 | - New pip install dependencies 63 | 64 | ### Changes 65 | - Now trying setup_requires instead of install_requires 66 | - Using setuptools instead of distutils 67 | 68 | ## [0.2] - 2015-03-22 69 | ### Added 70 | - README.md 71 | 72 | ### Changed 73 | - Examples fixed 74 | 75 | ## 0.1 - 2015-03-22 76 | ### Added 77 | - Initial release 78 | 79 | [0.3.1]: https://github.com/jordanh/neurio-python/compare/0.3.0...0.3.1 80 | [0.3.0]: https://github.com/jordanh/neurio-python/compare/0.2.10...0.3.0 81 | [0.3.0]: https://github.com/jordanh/neurio-python/compare/0.2.10...0.3.0 82 | [0.2.10]: https://github.com/jordanh/neurio-python/compare/0.2.9...0.2.10 83 | [0.2.9]: https://github.com/jordanh/neurio-python/compare/0.2.8...0.2.9 84 | [0.2.8]: https://github.com/jordanh/neurio-python/compare/0.2.7...0.2.8 85 | [0.2.7]: https://github.com/jordanh/neurio-python/compare/0.2.6...0.2.7 86 | [0.2.6]: https://github.com/jordanh/neurio-python/compare/0.2.4...0.2.6 87 | [0.2.4]: https://github.com/jordanh/neurio-python/compare/0.2.3...0.2.4 88 | [0.2.3]: https://github.com/jordanh/neurio-python/compare/0.2.2...0.2.3 89 | [0.2.2]: https://github.com/jordanh/neurio-python/compare/0.2...0.2.2 90 | [0.2]: https://github.com/jordanh/neurio-python/compare/0.1...0.2 91 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Neurio Energy Sensor and Appliance Info API Python Library 2 | 3 | This is the unofficial Python library for the [Neurio](http://neur.io) 4 | sensor real-time energy and appliance automation library. 5 | 6 | Use it to collect realtime energy production and consumption information for 7 | your home. Create smart home integrations and automations. 8 | Run machine learning experiments. 9 | 10 | The library currently supports: 11 | 12 | - OAuth 2 authentication (including token request) – `/v1/oauth2` 13 | - Consumption and production samples (live and historical) – `/v1/samples` 14 | - Energy consumption statistics rollups – `/v1/samples/stats` 15 | - Appliance detection and reporting - `/v1/appliances` 16 | - Local sensor sampling - `http:///current-sample` 17 | 18 | 19 | ## Installation 20 | 21 | The easiest way to install the module is via pip: 22 | 23 | $ sudo pip install neurio 24 | 25 | Or, clone the source repository and install it by hand: 26 | 27 | $ git clone https://github.com/jordanh/neurio-python neurio-python 28 | $ cd neurio-python 29 | $ sudo python setup.py install 30 | 31 | 32 | ## Getting Started 33 | 34 | Module documentation has been added to `neurio/__init.__.py` and is the 35 | canonical source of documentation. There are also a set of simple examples 36 | in `examples/`. 37 | 38 | Using the module is simple: 39 | 40 | ### 1. Request API Access Key from Neurio, Inc. 41 | 42 | You can create your own API Access Key here: 43 | https://my.neur.io/#settings/applications/register 44 | When creating your app, Homepage URL and Callback URL are optional. 45 | 46 | ### 2. Create Private Key File 47 | 48 | Create a file named `my_keys.py` (for example) and populate it with the 49 | `key` and `secret` information you received from Neurio. For your convenience, 50 | populate the sensor_id and location_id fields. Location and sensor ID can be 51 | 52 | 53 | obtained with get_user_information(): 54 | 55 | ```python 56 | key = "0123456789abcdef012345" 57 | secret = "0123456789abcdef012345" 58 | sensor_id = "0x0000123456789" 59 | location_id = "abcdEFG-hijkLMNOP" 60 | ``` 61 | 62 | ### 3. Write Your Application 63 | 64 | Here's an example application that authenticates using the secret 65 | information from `my_keys.py` and fetches the last real-time energy 66 | data received by the Neurio platform: 67 | 68 | ```python 69 | from __future__ import print_function 70 | import neurio 71 | import my_keys 72 | 73 | # Setup authentication: 74 | tp = neurio.TokenProvider(key=my_keys.key, secret=my_keys.secret) 75 | # Create client that can authenticate itself: 76 | nc = neurio.Client(token_provider=tp) 77 | # Get user information (including sensor ID and location ID) 78 | user_info = nc.get_user_information() 79 | 80 | print("Sensor ID %s, location ID %s" %(user_info["locations"][0]["sensors"][0]["sensorId"], 81 | user_info["locations"][0]["id"])) 82 | 83 | # Fetch sample: 84 | sample = nc.get_samples_live_last(sensor_id=user_info["locations"][0]["sensors"][0]["sensorId"]) 85 | 86 | print("Current power consumption: %d W" % (sample['consumptionPower'])) 87 | ``` 88 | 89 | That's it! 90 | 91 | ## Contributing 92 | 93 | Feel free to fork, submit pull requests, or send feedback. I'm excited 94 | to see what the world will create with Neurio. 95 | 96 | Issues can be submitted here: https://github.com/jordanh/neurio-python/issues 97 | 98 | ## Changelog 99 | 100 | See: [CHANGELOG.md](./CHANGELOG.md) 101 | 102 | ## Testing 103 | 104 | A series of unit tests have been written for this library. To run them, 105 | first create a file `tests/test_keys.py` containing your credentials 106 | (test_keys should contain sensor_id and location_id) and then: 107 | 108 | $ python -m unittest discover -s tests -p '*_test.py' -v 109 | 110 | ## License 111 | 112 | Copyright 2015, 2016 Jordan Husney 113 | 114 | Licensed under the Apache License, Version 2.0 (the "License"); 115 | you may not use this file except in compliance with the License. 116 | You may obtain a copy of the License at: http://www.apache.org/licenses/LICENSE-2.0 117 | 118 | Unless required by applicable law or agreed to in writing, software 119 | distributed under the License is distributed on an "AS IS" BASIS, 120 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 121 | See the License for the specific language governing permissions and 122 | limitations under the License. 123 | -------------------------------------------------------------------------------- /tests/samples_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | """ 3 | Copyright 2015, 2016 Jordan Husney 4 | 5 | Licensed under the Apache License, Version 2.0 (the "License"); 6 | you may not use this file except in compliance with the License. 7 | You may obtain a copy of the License at 8 | 9 | http://www.apache.org/licenses/LICENSE-2.0 10 | 11 | Unless required by applicable law or agreed to in writing, software 12 | distributed under the License is distributed on an "AS IS" BASIS, 13 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14 | See the License for the specific language governing permissions and 15 | limitations under the License. 16 | """ 17 | 18 | import sys 19 | sys.path.append(".") 20 | sys.path.append("..") 21 | 22 | import neurio 23 | import test_keys 24 | 25 | import unittest 26 | from datetime import datetime, timedelta 27 | 28 | class SamplesTest(unittest.TestCase): 29 | def setUp(self): 30 | self.sensor_id = test_keys.sensor_id 31 | self.tp = neurio.TokenProvider(key=test_keys.key, 32 | secret=test_keys.secret) 33 | self.nc = neurio.Client(token_provider=self.tp) 34 | 35 | def test_get_samples_live(self): 36 | samples = self.nc.get_samples_live(sensor_id=self.sensor_id) 37 | self.assertIsInstance(samples, list) 38 | self.assertGreater(len(samples), 0, "no samples received") 39 | self.assertIn('consumptionPower', samples[0]) 40 | self.assertIn('consumptionEnergy', samples[0]) 41 | self.assertIn('generationPower', samples[0]) 42 | self.assertIn('generationEnergy', samples[0]) 43 | self.assertIn('timestamp', samples[0]) 44 | 45 | def test_get_samples_live_with_last(self): 46 | one_min_ago = datetime.utcnow() - timedelta(minutes=1) 47 | one_min_ago = one_min_ago.replace(microsecond=0).isoformat() 48 | samples = self.nc.get_samples_live(sensor_id=self.sensor_id, 49 | last=one_min_ago) 50 | self.assertIsInstance(samples, list) 51 | self.assertGreater(len(samples), 0, "no samples received") 52 | self.assertAlmostEqual(len(samples), 60, delta=30, 53 | msg="should have received ~60 samples, instead received %d" % len(samples)) 54 | 55 | 56 | def test_get_samples_live_last(self): 57 | sample = self.nc.get_samples_live_last(self.sensor_id) 58 | self.assertIsInstance(sample, dict) 59 | self.assertIn('consumptionPower', sample) 60 | self.assertIn('consumptionEnergy', sample) 61 | self.assertIn('generationPower', sample) 62 | self.assertIn('generationEnergy', sample) 63 | self.assertIn('timestamp', sample) 64 | 65 | def test_get_samples(self): 66 | fifteen_min_ago = datetime.utcnow() - timedelta(minutes=15) 67 | fifteen_min_ago = fifteen_min_ago.replace(microsecond=0).isoformat() 68 | samples = self.nc.get_samples(self.sensor_id, fifteen_min_ago, 69 | "minutes", frequency=5) 70 | self.assertIsInstance(samples, list) 71 | self.assertIn('consumptionPower', samples[0]) 72 | self.assertIn('consumptionEnergy', samples[0]) 73 | self.assertIn('generationPower', samples[0]) 74 | self.assertIn('generationEnergy', samples[0]) 75 | self.assertIn('timestamp', samples[0]) 76 | 77 | def test_get_samples_error(self): 78 | fifteen_min_ago = datetime.utcnow() - timedelta(minutes=15) 79 | fifteen_min_ago = fifteen_min_ago.replace(microsecond=0).isoformat() 80 | samples = self.nc.get_samples(self.sensor_id, fifteen_min_ago, 81 | "seconds", frequency=5) 82 | # this should fail, historical sample resolution not available: 83 | self.assertIsInstance(samples, dict) 84 | self.assertIn('status', samples) 85 | self.assertIn('errors', samples) 86 | 87 | def test_get_samples_full(self): 88 | fifteen_min_ago = datetime.utcnow() - timedelta(minutes=15) 89 | fifteen_min_ago = fifteen_min_ago.replace(microsecond=0).isoformat() 90 | samples = self.nc.get_samples(self.sensor_id, fifteen_min_ago, 91 | "minutes", frequency=5, full=True) 92 | self.assertIsInstance(samples, list) 93 | self.assertIn('timestamp', samples[0]) 94 | self.assertIn('channelSamples', samples[0]) 95 | self.assertIsInstance(samples[0]['channelSamples'], list) 96 | 97 | def test_get_samples_stats(self): 98 | fifteen_min_ago = datetime.utcnow() - timedelta(minutes=15) 99 | fifteen_min_ago = fifteen_min_ago.replace(microsecond=0).isoformat() 100 | stats = self.nc.get_samples_stats(self.sensor_id, fifteen_min_ago, 101 | "minutes", frequency=5) 102 | self.assertIsInstance(stats, list) 103 | self.assertIn('start', stats[0]) 104 | self.assertIn('end', stats[0]) 105 | self.assertIn('consumptionEnergy', stats[0]) 106 | 107 | 108 | if __name__ == '__main__': 109 | unittest.main() 110 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | 2 | Apache License 3 | Version 2.0, January 2004 4 | http://www.apache.org/licenses/ 5 | 6 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION 7 | 8 | 1. Definitions. 9 | 10 | "License" shall mean the terms and conditions for use, reproduction, 11 | and distribution as defined by Sections 1 through 9 of this document. 12 | 13 | "Licensor" shall mean the copyright owner or entity authorized by 14 | the copyright owner that is granting the License. 15 | 16 | "Legal Entity" shall mean the union of the acting entity and all 17 | other entities that control, are controlled by, or are under common 18 | control with that entity. For the purposes of this definition, 19 | "control" means (i) the power, direct or indirect, to cause the 20 | direction or management of such entity, whether by contract or 21 | otherwise, or (ii) ownership of fifty percent (50%) or more of the 22 | outstanding shares, or (iii) beneficial ownership of such entity. 23 | 24 | "You" (or "Your") shall mean an individual or Legal Entity 25 | exercising permissions granted by this License. 26 | 27 | "Source" form shall mean the preferred form for making modifications, 28 | including but not limited to software source code, documentation 29 | source, and configuration files. 30 | 31 | "Object" form shall mean any form resulting from mechanical 32 | transformation or translation of a Source form, including but 33 | not limited to compiled object code, generated documentation, 34 | and conversions to other media types. 35 | 36 | "Work" shall mean the work of authorship, whether in Source or 37 | Object form, made available under the License, as indicated by a 38 | copyright notice that is included in or attached to the work 39 | (an example is provided in the Appendix below). 40 | 41 | "Derivative Works" shall mean any work, whether in Source or Object 42 | form, that is based on (or derived from) the Work and for which the 43 | editorial revisions, annotations, elaborations, or other modifications 44 | represent, as a whole, an original work of authorship. For the purposes 45 | of this License, Derivative Works shall not include works that remain 46 | separable from, or merely link (or bind by name) to the interfaces of, 47 | the Work and Derivative Works thereof. 48 | 49 | "Contribution" shall mean any work of authorship, including 50 | the original version of the Work and any modifications or additions 51 | to that Work or Derivative Works thereof, that is intentionally 52 | submitted to Licensor for inclusion in the Work by the copyright owner 53 | or by an individual or Legal Entity authorized to submit on behalf of 54 | the copyright owner. For the purposes of this definition, "submitted" 55 | means any form of electronic, verbal, or written communication sent 56 | to the Licensor or its representatives, including but not limited to 57 | communication on electronic mailing lists, source code control systems, 58 | and issue tracking systems that are managed by, or on behalf of, the 59 | Licensor for the purpose of discussing and improving the Work, but 60 | excluding communication that is conspicuously marked or otherwise 61 | designated in writing by the copyright owner as "Not a Contribution." 62 | 63 | "Contributor" shall mean Licensor and any individual or Legal Entity 64 | on behalf of whom a Contribution has been received by Licensor and 65 | subsequently incorporated within the Work. 66 | 67 | 2. Grant of Copyright License. Subject to the terms and conditions of 68 | this License, each Contributor hereby grants to You a perpetual, 69 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 70 | copyright license to reproduce, prepare Derivative Works of, 71 | publicly display, publicly perform, sublicense, and distribute the 72 | Work and such Derivative Works in Source or Object form. 73 | 74 | 3. Grant of Patent License. Subject to the terms and conditions of 75 | this License, each Contributor hereby grants to You a perpetual, 76 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable 77 | (except as stated in this section) patent license to make, have made, 78 | use, offer to sell, sell, import, and otherwise transfer the Work, 79 | where such license applies only to those patent claims licensable 80 | by such Contributor that are necessarily infringed by their 81 | Contribution(s) alone or by combination of their Contribution(s) 82 | with the Work to which such Contribution(s) was submitted. If You 83 | institute patent litigation against any entity (including a 84 | cross-claim or counterclaim in a lawsuit) alleging that the Work 85 | or a Contribution incorporated within the Work constitutes direct 86 | or contributory patent infringement, then any patent licenses 87 | granted to You under this License for that Work shall terminate 88 | as of the date such litigation is filed. 89 | 90 | 4. Redistribution. You may reproduce and distribute copies of the 91 | Work or Derivative Works thereof in any medium, with or without 92 | modifications, and in Source or Object form, provided that You 93 | meet the following conditions: 94 | 95 | (a) You must give any other recipients of the Work or 96 | Derivative Works a copy of this License; and 97 | 98 | (b) You must cause any modified files to carry prominent notices 99 | stating that You changed the files; and 100 | 101 | (c) You must retain, in the Source form of any Derivative Works 102 | that You distribute, all copyright, patent, trademark, and 103 | attribution notices from the Source form of the Work, 104 | excluding those notices that do not pertain to any part of 105 | the Derivative Works; and 106 | 107 | (d) If the Work includes a "NOTICE" text file as part of its 108 | distribution, then any Derivative Works that You distribute must 109 | include a readable copy of the attribution notices contained 110 | within such NOTICE file, excluding those notices that do not 111 | pertain to any part of the Derivative Works, in at least one 112 | of the following places: within a NOTICE text file distributed 113 | as part of the Derivative Works; within the Source form or 114 | documentation, if provided along with the Derivative Works; or, 115 | within a display generated by the Derivative Works, if and 116 | wherever such third-party notices normally appear. The contents 117 | of the NOTICE file are for informational purposes only and 118 | do not modify the License. You may add Your own attribution 119 | notices within Derivative Works that You distribute, alongside 120 | or as an addendum to the NOTICE text from the Work, provided 121 | that such additional attribution notices cannot be construed 122 | as modifying the License. 123 | 124 | You may add Your own copyright statement to Your modifications and 125 | may provide additional or different license terms and conditions 126 | for use, reproduction, or distribution of Your modifications, or 127 | for any such Derivative Works as a whole, provided Your use, 128 | reproduction, and distribution of the Work otherwise complies with 129 | the conditions stated in this License. 130 | 131 | 5. Submission of Contributions. Unless You explicitly state otherwise, 132 | any Contribution intentionally submitted for inclusion in the Work 133 | by You to the Licensor shall be under the terms and conditions of 134 | this License, without any additional terms or conditions. 135 | Notwithstanding the above, nothing herein shall supersede or modify 136 | the terms of any separate license agreement you may have executed 137 | with Licensor regarding such Contributions. 138 | 139 | 6. Trademarks. This License does not grant permission to use the trade 140 | names, trademarks, service marks, or product names of the Licensor, 141 | except as required for reasonable and customary use in describing the 142 | origin of the Work and reproducing the content of the NOTICE file. 143 | 144 | 7. Disclaimer of Warranty. Unless required by applicable law or 145 | agreed to in writing, Licensor provides the Work (and each 146 | Contributor provides its Contributions) on an "AS IS" BASIS, 147 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or 148 | implied, including, without limitation, any warranties or conditions 149 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A 150 | PARTICULAR PURPOSE. You are solely responsible for determining the 151 | appropriateness of using or redistributing the Work and assume any 152 | risks associated with Your exercise of permissions under this License. 153 | 154 | 8. Limitation of Liability. In no event and under no legal theory, 155 | whether in tort (including negligence), contract, or otherwise, 156 | unless required by applicable law (such as deliberate and grossly 157 | negligent acts) or agreed to in writing, shall any Contributor be 158 | liable to You for damages, including any direct, indirect, special, 159 | incidental, or consequential damages of any character arising as a 160 | result of this License or out of the use or inability to use the 161 | Work (including but not limited to damages for loss of goodwill, 162 | work stoppage, computer failure or malfunction, or any and all 163 | other commercial damages or losses), even if such Contributor 164 | has been advised of the possibility of such damages. 165 | 166 | 9. Accepting Warranty or Additional Liability. While redistributing 167 | the Work or Derivative Works thereof, You may choose to offer, 168 | and charge a fee for, acceptance of support, warranty, indemnity, 169 | or other liability obligations and/or rights consistent with this 170 | License. However, in accepting such obligations, You may act only 171 | on Your own behalf and on Your sole responsibility, not on behalf 172 | of any other Contributor, and only if You agree to indemnify, 173 | defend, and hold each Contributor harmless for any liability 174 | incurred by, or claims asserted against, such Contributor by reason 175 | of your accepting any such warranty or additional liability. 176 | 177 | END OF TERMS AND CONDITIONS 178 | -------------------------------------------------------------------------------- /neurio/__init__.py: -------------------------------------------------------------------------------- 1 | """ 2 | Copyright 2015, 2016 Jordan Husney 3 | 4 | Licensed under the Apache License, Version 2.0 (the "License"); 5 | you may not use this file except in compliance with the License. 6 | You may obtain a copy of the License at 7 | 8 | http://www.apache.org/licenses/LICENSE-2.0 9 | 10 | Unless required by applicable law or agreed to in writing, software 11 | distributed under the License is distributed on an "AS IS" BASIS, 12 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 | See the License for the specific language governing permissions and 14 | limitations under the License. 15 | """ 16 | 17 | import requests 18 | from base64 import b64encode 19 | import re 20 | 21 | try: 22 | from urllib import urlencode 23 | except ImportError: 24 | from urllib.parse import urlencode 25 | 26 | try: 27 | from urlparse import urlparse, parse_qsl, urlunparse 28 | except ImportError: 29 | from urllib.parse import urlparse, parse_qsl, urlunparse 30 | 31 | __version__ = "0.3.1" 32 | 33 | class TokenProvider(object): 34 | __key = None 35 | __secret = None 36 | __token = None 37 | 38 | def __init__(self, key, secret): 39 | """Handles token authentication for Neurio Client. 40 | 41 | Args: 42 | key (string): your Neurio API key 43 | secret (string): your Neurio API secret 44 | """ 45 | self.__key = key 46 | self.__secret = secret 47 | 48 | if self.__key is None or self.__secret is None: 49 | raise ValueError("Key and secret must be set.") 50 | 51 | def get_token(self): 52 | """Performs Neurio API token authentication using provided key and secret. 53 | 54 | Note: 55 | This method is generally not called by hand; rather it is usually 56 | called as-needed by a Neurio Client object. 57 | 58 | Returns: 59 | string: the access token 60 | """ 61 | if self.__token is not None: 62 | return self.__token 63 | 64 | url = "https://api.neur.io/v1/oauth2/token" 65 | 66 | creds = b64encode(":".join([self.__key,self.__secret]).encode()).decode() 67 | 68 | headers = { 69 | "Authorization": " ".join(["Basic", creds]), 70 | } 71 | payload = { 72 | "grant_type": "client_credentials" 73 | } 74 | 75 | r = requests.post(url, data=payload, headers=headers) 76 | 77 | self.__token = r.json()["access_token"] 78 | 79 | return self.__token 80 | 81 | 82 | class Client(object): 83 | __token = None 84 | 85 | def __init__(self, token_provider): 86 | """The Neurio API client. 87 | 88 | Args: 89 | token_provider (TokenProvider): object providing authentication services 90 | """ 91 | if token_provider is None: 92 | raise ValueError("token_provider is required") 93 | if not isinstance(token_provider, TokenProvider): 94 | raise ValueError("token_provider must be instance of TokenProvider") 95 | 96 | self.__token = token_provider.get_token() 97 | 98 | def __gen_headers(self): 99 | """Utility method adding authentication token to requests.""" 100 | headers = { 101 | "Authorization": " ".join(["Bearer", self.__token]) 102 | } 103 | 104 | return headers 105 | 106 | def __append_url_params(self, url, params): 107 | """Utility method formatting url request parameters.""" 108 | url_parts = list(urlparse(url)) 109 | query = dict(parse_qsl(url_parts[4])) 110 | query.update(params) 111 | url_parts[4] = urlencode(query) 112 | 113 | return urlunparse(url_parts) 114 | 115 | def get_appliance(self, appliance_id): 116 | """Get the information for a specified appliance 117 | 118 | Args: 119 | appliance_id (string): identifiying string of appliance 120 | 121 | Returns: 122 | list: dictionary object containing information about the specified appliance 123 | """ 124 | url = "https://api.neur.io/v1/appliances/%s"%(appliance_id) 125 | 126 | headers = self.__gen_headers() 127 | headers["Content-Type"] = "application/json" 128 | 129 | r = requests.get(url, headers=headers) 130 | return r.json() 131 | 132 | def get_appliances(self, location_id): 133 | """Get the appliances added for a specified location. 134 | 135 | Args: 136 | location_id (string): identifiying string of appliance 137 | 138 | Returns: 139 | list: dictionary objects containing appliances data 140 | """ 141 | url = "https://api.neur.io/v1/appliances" 142 | 143 | headers = self.__gen_headers() 144 | headers["Content-Type"] = "application/json" 145 | 146 | params = { 147 | "locationId": location_id, 148 | } 149 | url = self.__append_url_params(url, params) 150 | 151 | r = requests.get(url, headers=headers) 152 | return r.json() 153 | 154 | def get_appliance_event_by_location(self, location_id, start, end, per_page=None, page=None, min_power=None): 155 | """Get appliance events by location Id. 156 | 157 | Args: 158 | location_id (string): hexadecimal id of the sensor to query, e.g. 159 | ``0x0013A20040B65FAD`` 160 | start (string): ISO 8601 start time for getting the events of appliances. 161 | end (string): ISO 8601 stop time for getting the events of appliances. 162 | Cannot be larger than 1 day from start time 163 | min_power (string): The minimum average power (in watts) for filtering. 164 | Only events with an average power above this value will be returned. 165 | (default: 400) 166 | per_page (string, optional): the number of returned results per page 167 | (min 1, max 500) (default: 10) 168 | page (string, optional): the page number to return (min 1, max 100000) 169 | (default: 1) 170 | 171 | Returns: 172 | list: dictionary objects containing appliance events meeting specified criteria 173 | """ 174 | url = "https://api.neur.io/v1/appliances/events" 175 | 176 | headers = self.__gen_headers() 177 | headers["Content-Type"] = "application/json" 178 | 179 | params = { 180 | "locationId": location_id, 181 | "start": start, 182 | "end": end 183 | } 184 | if min_power: 185 | params["minPower"] = min_power 186 | if per_page: 187 | params["perPage"] = per_page 188 | if page: 189 | params["page"] = page 190 | url = self.__append_url_params(url, params) 191 | 192 | r = requests.get(url, headers=headers) 193 | return r.json() 194 | 195 | def get_appliance_event_after_time(self, location_id, since, per_page=None, page=None, min_power=None): 196 | """Get appliance events by location Id after defined time. 197 | 198 | Args: 199 | location_id (string): hexadecimal id of the sensor to query, e.g. 200 | ``0x0013A20040B65FAD`` 201 | since (string): ISO 8601 start time for getting the events that are created or updated after it. 202 | Maxiumim value allowed is 1 day from the current time. 203 | min_power (string): The minimum average power (in watts) for filtering. 204 | Only events with an average power above this value will be returned. 205 | (default: 400) 206 | per_page (string, optional): the number of returned results per page 207 | (min 1, max 500) (default: 10) 208 | page (string, optional): the page number to return (min 1, max 100000) 209 | (default: 1) 210 | 211 | Returns: 212 | list: dictionary objects containing appliance events meeting specified criteria 213 | """ 214 | url = "https://api.neur.io/v1/appliances/events" 215 | 216 | headers = self.__gen_headers() 217 | headers["Content-Type"] = "application/json" 218 | 219 | params = { 220 | "locationId": location_id, 221 | "since": since 222 | } 223 | if min_power: 224 | params["minPower"] = min_power 225 | if per_page: 226 | params["perPage"] = per_page 227 | if page: 228 | params["page"] = page 229 | url = self.__append_url_params(url, params) 230 | 231 | r = requests.get(url, headers=headers) 232 | return r.json() 233 | 234 | def get_appliance_event_by_appliance(self, appliance_id, start, end, per_page=None, page=None, min_power=None): 235 | """Get appliance events by appliance Id. 236 | 237 | Args: 238 | appliance_id (string): hexadecimal id of the appliance to query, e.g. 239 | ``0x0013A20040B65FAD`` 240 | start (string): ISO 8601 start time for getting the events of appliances. 241 | end (string): ISO 8601 stop time for getting the events of appliances. 242 | Cannot be larger than 1 day from start time 243 | min_power (string): The minimum average power (in watts) for filtering. 244 | Only events with an average power above this value will be returned. 245 | (default: 400) 246 | per_page (string, optional): the number of returned results per page 247 | (min 1, max 500) (default: 10) 248 | page (string, optional): the page number to return (min 1, max 100000) 249 | (default: 1) 250 | 251 | Returns: 252 | list: dictionary objects containing appliance events meeting specified criteria 253 | """ 254 | url = "https://api.neur.io/v1/appliances/events" 255 | 256 | headers = self.__gen_headers() 257 | headers["Content-Type"] = "application/json" 258 | 259 | params = { 260 | "applianceId": appliance_id, 261 | "start": start, 262 | "end": end 263 | } 264 | if min_power: 265 | params["minPower"] = min_power 266 | if per_page: 267 | params["perPage"] = per_page 268 | if page: 269 | params["page"] = page 270 | url = self.__append_url_params(url, params) 271 | 272 | r = requests.get(url, headers=headers) 273 | return r.json() 274 | 275 | def get_appliance_stats_by_appliance(self, appliance_id, start, end, granularity=None, per_page=None, page=None, 276 | min_power=None): 277 | """Get appliance usage data for a single appliance within a given time range. 278 | Stats are generated by fetching appliance events that match the supplied 279 | criteria and then aggregating them together based on the granularity 280 | specified with the request. 281 | 282 | Note: 283 | This endpoint uses the location's time zone when generating time intervals 284 | for the stats, which is relevant if that time zone uses daylight saving 285 | time (some days will be 23 or 25 hours long). 286 | 287 | Args: 288 | appliance_id (string): hexadecimal id of the appliance to query, e.g. 289 | ``0x0013A20040B65FAD`` 290 | start (string): ISO 8601 start time for getting the events of appliances. 291 | end (string): ISO 8601 stop time for getting the events of appliances. 292 | Cannot be larger than 1 month from start time 293 | granularity (string): granularity of stats. If the granularity is 294 | 'unknown', the stats for the appliances between the start and 295 | end time is returned.; 296 | must be one of "minutes", "hours", "days", "weeks", "months", or "unknown" 297 | (default: days) 298 | min_power (string): The minimum average power (in watts) for filtering. 299 | Only events with an average power above this value will be returned. 300 | (default: 400) 301 | per_page (string, optional): the number of returned results per page 302 | (min 1, max 500) (default: 10) 303 | page (string, optional): the page number to return (min 1, max 100000) 304 | (default: 1) 305 | 306 | Returns: 307 | list: dictionary objects containing appliance events meeting specified criteria 308 | """ 309 | url = "https://api.neur.io/v1/appliances/stats" 310 | 311 | headers = self.__gen_headers() 312 | headers["Content-Type"] = "application/json" 313 | 314 | params = { 315 | "applianceId": appliance_id, 316 | "start": start, 317 | "end": end 318 | } 319 | if granularity: 320 | params["granularity"] = granularity 321 | if min_power: 322 | params["minPower"] = min_power 323 | if per_page: 324 | params["perPage"] = per_page 325 | if page: 326 | params["page"] = page 327 | url = self.__append_url_params(url, params) 328 | 329 | r = requests.get(url, headers=headers) 330 | return r.json() 331 | 332 | def get_appliance_stats_by_location(self, location_id, start, end, granularity=None, per_page=None, page=None, 333 | min_power=None): 334 | """Get appliance usage data for a given location within a given time range. 335 | Stats are generated by fetching appliance events that match the supplied 336 | criteria and then aggregating them together based on the granularity 337 | specified with the request. 338 | 339 | Note: 340 | This endpoint uses the location's time zone when generating time intervals 341 | for the stats, which is relevant if that time zone uses daylight saving 342 | time (some days will be 23 or 25 hours long). 343 | 344 | Args: 345 | location_id (string): hexadecimal id of the sensor to query, e.g. 346 | ``0x0013A20040B65FAD`` 347 | start (string): ISO 8601 start time for getting the events of appliances. 348 | end (string): ISO 8601 stop time for getting the events of appliances. 349 | Cannot be larger than 1 month from start time 350 | granularity (string): granularity of stats. If the granularity is 351 | 'unknown', the stats for the appliances between the start and 352 | end time is returned.; 353 | must be one of "minutes", "hours", "days", "weeks", "months", or "unknown" 354 | (default: days) 355 | min_power (string): The minimum average power (in watts) for filtering. 356 | Only events with an average power above this value will be returned. 357 | (default: 400) 358 | per_page (string, optional): the number of returned results per page 359 | (min 1, max 500) (default: 10) 360 | page (string, optional): the page number to return (min 1, max 100000) 361 | (default: 1) 362 | 363 | Returns: 364 | list: dictionary objects containing appliance events meeting specified criteria 365 | """ 366 | url = "https://api.neur.io/v1/appliances/stats" 367 | 368 | headers = self.__gen_headers() 369 | headers["Content-Type"] = "application/json" 370 | 371 | params = { 372 | "locationId": location_id, 373 | "start": start, 374 | "end": end 375 | } 376 | if granularity: 377 | params["granularity"] = granularity 378 | if min_power: 379 | params["minPower"] = min_power 380 | if per_page: 381 | params["perPage"] = per_page 382 | if page: 383 | params["page"] = page 384 | url = self.__append_url_params(url, params) 385 | 386 | r = requests.get(url, headers=headers) 387 | return r.json() 388 | 389 | @staticmethod 390 | def get_local_current_sample(ip): 391 | """Gets current sample from *local* Neurio device IP address. 392 | 393 | This is a static method. It doesn't require a token to authenticate. 394 | 395 | Note, call get_user_information to determine local Neurio IP addresses. 396 | 397 | Args: 398 | ip (string): address of local Neurio device 399 | 400 | Returns: 401 | dictionary object containing current sample information 402 | """ 403 | valid_ip_pat = re.compile( 404 | "^((25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)$" 405 | ) 406 | if not valid_ip_pat.match(ip): 407 | raise ValueError("ip address invalid") 408 | 409 | url = "http://%s/current-sample" % (ip) 410 | headers = { "Content-Type": "application/json" } 411 | 412 | r = requests.get(url, headers=headers) 413 | return r.json() 414 | 415 | def get_samples_live(self, sensor_id, last=None): 416 | """Get recent samples, one sample per second for up to the last 2 minutes. 417 | 418 | Args: 419 | sensor_id (string): hexadecimal id of the sensor to query, e.g. 420 | ``0x0013A20040B65FAD`` 421 | last (string): starting range, as ISO8601 timestamp 422 | 423 | Returns: 424 | list: dictionary objects containing sample data 425 | """ 426 | url = "https://api.neur.io/v1/samples/live" 427 | 428 | headers = self.__gen_headers() 429 | headers["Content-Type"] = "application/json" 430 | 431 | params = { "sensorId": sensor_id } 432 | if last: 433 | params["last"] = last 434 | url = self.__append_url_params(url, params) 435 | 436 | r = requests.get(url, headers=headers) 437 | return r.json() 438 | 439 | def get_samples_live_last(self, sensor_id): 440 | """Get the last sample recorded by the sensor. 441 | 442 | Args: 443 | sensor_id (string): hexadecimal id of the sensor to query, e.g. 444 | ``0x0013A20040B65FAD`` 445 | 446 | Returns: 447 | list: dictionary objects containing sample data 448 | """ 449 | url = "https://api.neur.io/v1/samples/live/last" 450 | 451 | headers = self.__gen_headers() 452 | headers["Content-Type"] = "application/json" 453 | 454 | params = { "sensorId": sensor_id } 455 | url = self.__append_url_params(url, params) 456 | 457 | r = requests.get(url, headers=headers) 458 | return r.json() 459 | 460 | def get_samples(self, sensor_id, start, granularity, end=None, 461 | frequency=None, per_page=None, page=None, 462 | full=False): 463 | """Get a sensor's samples for a specified time interval. 464 | 465 | Args: 466 | sensor_id (string): hexadecimal id of the sensor to query, e.g. 467 | ``0x0013A20040B65FAD`` 468 | start (string): ISO 8601 start time of sampling; depends on the 469 | ``granularity`` parameter value, the maximum supported time ranges are: 470 | 1 day for minutes or hours granularities, 1 month for days, 471 | 6 months for weeks, 1 year for months granularity, and 10 years for 472 | years granularity 473 | granularity (string): granularity of the sampled data; must be one of 474 | "minutes", "hours", "days", "weeks", "months", or "years" 475 | end (string, optional): ISO 8601 stop time for sampling; should be later 476 | than start time (default: the current time) 477 | frequency (string, optional): frequency of the sampled data (e.g. with 478 | granularity set to days, a value of 3 will result in a sample for every 479 | third day, should be a multiple of 5 when using minutes granularity) 480 | (default: 1) (example: "1, 5") 481 | per_page (string, optional): the number of returned results per page 482 | (min 1, max 500) (default: 10) 483 | page (string, optional): the page number to return (min 1, max 100000) 484 | (default: 1) 485 | full (bool, optional): include additional information per sample 486 | (default: False) 487 | 488 | Returns: 489 | list: dictionary objects containing sample data 490 | """ 491 | url = "https://api.neur.io/v1/samples" 492 | if full: 493 | url = "https://api.neur.io/v1/samples/full" 494 | 495 | headers = self.__gen_headers() 496 | headers["Content-Type"] = "application/json" 497 | 498 | params = { 499 | "sensorId": sensor_id, 500 | "start": start, 501 | "granularity": granularity 502 | } 503 | if end: 504 | params["end"] = end 505 | if frequency: 506 | params["frequency"] = frequency 507 | if per_page: 508 | params["perPage"] = per_page 509 | if page: 510 | params["page"] = page 511 | url = self.__append_url_params(url, params) 512 | 513 | r = requests.get(url, headers=headers) 514 | return r.json() 515 | 516 | def get_samples_stats(self, sensor_id, start, granularity, end=None, 517 | frequency=None, per_page=None, page=None): 518 | """Get brief stats for energy consumed in a given time interval. 519 | 520 | Note: 521 | Note: this endpoint uses the sensor location's time zone when 522 | generating time intervals for the stats, which is relevant if that time 523 | zone uses daylight saving time (some days will be 23 or 25 hours long). 524 | 525 | Args: 526 | sensor_id (string): hexadecimal id of the sensor to query, e.g. 527 | ``0x0013A20040B65FAD`` 528 | start (string): ISO 8601 start time of sampling; depends on the 529 | ``granularity`` parameter value, the maximum supported time ranges are: 530 | 1 day for minutes or hours granularities, 1 month for days, 531 | 6 months for weeks, 1 year for months granularity, and 10 years for 532 | years granularity 533 | granularity (string): granularity of the sampled data; must be one of 534 | "minutes", "hours", "days", "weeks", "months", or "years" 535 | end (string, optional): ISO 8601 stop time for sampling; should be later 536 | than start time (default: the current time) 537 | frequency (string, optional): frequency of the sampled data (e.g. with 538 | granularity set to days, a value of 3 will result in a sample for every 539 | third day, should be a multiple of 5 when using minutes granularity) 540 | (default: 1) (example: "1, 5") 541 | per_page (string, optional): the number of returned results per page 542 | (min 1, max 500) (default: 10) 543 | page (string, optional): the page number to return (min 1, max 100000) 544 | (default: 1) 545 | 546 | Returns: 547 | list: dictionary objects containing sample statistics data 548 | """ 549 | url = "https://api.neur.io/v1/samples/stats" 550 | 551 | headers = self.__gen_headers() 552 | headers["Content-Type"] = "application/json" 553 | 554 | params = { 555 | "sensorId": sensor_id, 556 | "start": start, 557 | "granularity": granularity 558 | } 559 | if end: 560 | params["end"] = end 561 | if frequency: 562 | params["frequency"] = frequency 563 | if per_page: 564 | params["perPage"] = per_page 565 | if page: 566 | params["page"] = page 567 | url = self.__append_url_params(url, params) 568 | 569 | r = requests.get(url, headers=headers) 570 | return r.json() 571 | 572 | def get_user_information(self): 573 | """Gets the current user information, including sensor ID 574 | 575 | Args: 576 | None 577 | 578 | Returns: 579 | dictionary object containing information about the current user 580 | """ 581 | url = "https://api.neur.io/v1/users/current" 582 | 583 | headers = self.__gen_headers() 584 | headers["Content-Type"] = "application/json" 585 | 586 | r = requests.get(url, headers=headers) 587 | return r.json() 588 | --------------------------------------------------------------------------------