Skip to content

Commit 2de04e7

Browse files
committed
first version
1 parent a404b10 commit 2de04e7

7 files changed

Lines changed: 260 additions & 2 deletions

File tree

CHANGES.rst

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
=======
2+
CHANGES
3+
=======
4+
5+
v0.1.0
6+
-------
7+
8+
* initial release
9+
* supports querying the latest values and pushing data
10+
* compatible with GSN v2.0.0
11+

README.md

Lines changed: 0 additions & 2 deletions
This file was deleted.

README.rst

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
===============================
2+
Python wrapper for the Global Sensor Networks API
3+
===============================
4+
5+
.. contents:: **Table of Contents**
6+
7+
-------------------------
8+
Installation
9+
-------------------------
10+
11+
> pip install gsn
12+
13+
-------------------------
14+
Usage
15+
-------------------------
16+
17+
The wrapper uses the `sanction`_ Oauth2 library to authenticate against GSN services.
18+
Before starting you must create a "client" on GSN services and get the client_id, client_secret and redirect_uri.
19+
As we will be using client_credentials, the client must be linked to the user from which it will inherit the access rights.
20+
The value of redirect uri is not used, but must nevertheless match.
21+
22+
Then in your python code, you can use it this way::
23+
24+
> import gsn
25+
> a = gsn.API(service_url="http://localhost:9000/ws", client_id="client", client_secret="secret", redirect_uri="http://localhost")
26+
> s = a.get_latest_values("push")
27+
> s.values = [[1469959498000, 18]]
28+
> r = a.push_values(s)
29+
30+
.. _sanction: https://github.com/demianbrecht/sanction
31+
32+
Sensor object
33+
===============================
34+
35+
This object abstract the json representation used by GSN for the stream elements. It has the following fields:
36+
37+
* fields: list of triples representing the name, type and units
38+
* name: name of the sensor
39+
* values: list of lists. The inner lists must have the same size as the fields list and represent the data of a stream element.
40+
* location: (optional) a triple representing the location of the sensor as latitude, longitude, altitude.

gsn/__init__.py

Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
from sanction import Client, transport_headers
2+
from .sensor import Sensor
3+
try:
4+
from urllib2 import HTTPError
5+
except:
6+
from urllib.error import HTTPError
7+
8+
9+
class API(object):
10+
""" Global Sensor Networks api client object
11+
"""
12+
13+
def __init__(self, service_url=None, client_id=None, client_secret=None, redirect_uri=None):
14+
""" Instantiates a GSN API client to authorize and authenticate a user
15+
:param service_url: The authorization endpoint provided by GSN
16+
services.
17+
:param client_id: The client ID.
18+
:param client_secret: The client secret.
19+
"""
20+
assert service_url is not None
21+
assert client_id is not None and client_secret is not None
22+
23+
self.client = Client(token_endpoint="{}/oauth2/token".format(service_url),
24+
resource_endpoint="{}/api".format(service_url),
25+
client_id=client_id, client_secret=client_secret,
26+
token_transport=transport_headers
27+
)
28+
self.client.request_token(grant_type='client_credentials', redirect_uri=redirect_uri)
29+
30+
def get_latest_values(self, vs_name=None):
31+
""" Query the API to get the latest values of a given virtual sensor.
32+
:param vs_name: The name of the virtual sensor.
33+
:returns: A Sensor object.
34+
"""
35+
assert vs_name is not None
36+
37+
data = self.client.request("/sensors/{}?latestValues=True".format(vs_name))
38+
return Sensor(geojson_object=data)
39+
40+
def push_values(self, sensor_data=None):
41+
""" Push sensor data into GSN's API. The corresponding virtual sensor
42+
must be of type zeromq-push.
43+
:param sensor_data: A Sensor object containing the sensor values.
44+
:returns: The server response.
45+
"""
46+
47+
assert sensor_data is not None
48+
49+
try:
50+
res = self.client.request("/sensors/{}/data".format(sensor_data.name),
51+
data=sensor_data.to_geojson().encode('utf_8'),
52+
headers={'Content-type': 'application/json'})
53+
except HTTPError as e:
54+
return e.readlines()
55+
return res

gsn/sensor.py

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
from json import dumps
2+
3+
4+
class Sensor(object):
5+
6+
def __init__(self, geojson_object=None, name=None, fields=None, location=None):
7+
""" Instantiates a Sensor object defining its structure
8+
either from a geojson object or from the related parameters.
9+
:param geojson_object: A dict loaded from a geojson object.
10+
:param name: The name of the sensor. Must only by used in
11+
conjunction with fields.
12+
:param fields: A list of triples containing the name, data-type
13+
and units of the sensor fields, as strings.
14+
See GSN's documentation for a list of data-types.
15+
:param location: A triple giving the latitude, longitude and
16+
altitude of the sensor, not mandatory.
17+
"""
18+
assert geojson_object is None or (fields is None and name is None)
19+
20+
if geojson_object:
21+
self.fields = [(f['name'], f['type'], f['unit'])
22+
for f in geojson_object['properties']['fields']]
23+
self.name = geojson_object['properties']['vs_name']
24+
self.values = geojson_object['properties']['values']
25+
self.location = geojson_object['geometry']['coordinates']
26+
else:
27+
self.fields = fields
28+
self.name = name
29+
self.values = []
30+
self.location = location
31+
32+
def to_geojson(self):
33+
34+
r = {"type": "Feature",
35+
"properties": {"vs_name": self.name,
36+
"values": self.values,
37+
"fields": [{"name": f[0], "type": f[1], "unit": f[2]} for f in self.fields],
38+
"stats": {},
39+
"geographical": "",
40+
"description": "Generated from python-gsn client"
41+
},
42+
"geometry": {"type": "Point", "coordinates": self.location},
43+
"total_size": 1,
44+
"page_size": 1
45+
}
46+
return dumps(r)

setup.cfg

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[bdist_wheel]
2+
universal=1

setup.py

Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
"""A python wrapper for Global Sensor Networks API
2+
https://github.com/LSIR/python-gsn
3+
"""
4+
5+
# Always prefer setuptools over distutils
6+
from setuptools import setup, find_packages
7+
# To use a consistent encoding
8+
from codecs import open
9+
from os import path
10+
11+
here = path.abspath(path.dirname(__file__))
12+
13+
# Get the long description from the README file
14+
with open(path.join(here, 'README.rst'), encoding='utf-8') as f:
15+
long_description = f.read()
16+
17+
with open(path.join(here, 'CHANGES.rst'), encoding='utf-8') as f:
18+
long_description += f.read()
19+
20+
setup(
21+
name='gsn',
22+
23+
# Versions should comply with PEP440.
24+
version='0.1.0',
25+
26+
description='A python wrapper for Global Sensor Networks API',
27+
long_description=long_description,
28+
29+
# The project's main homepage.
30+
url='https://github.com/LSIR/python-gsn',
31+
32+
# Author details
33+
author='The GSN Team',
34+
author_email='gsn@epfl.ch',
35+
36+
# Choose your license
37+
license='GPLv3+',
38+
39+
# See https://pypi.python.org/pypi?%3Aaction=list_classifiers
40+
classifiers=[
41+
# How mature is this project? Common values are
42+
# 3 - Alpha
43+
# 4 - Beta
44+
# 5 - Production/Stable
45+
'Development Status :: 3 - Alpha',
46+
47+
# Indicate who your project is intended for
48+
'Intended Audience :: Developers',
49+
'Topic :: Software Development :: Libraries :: Python Modules',
50+
51+
# Pick your license as you wish (should match "license" above)
52+
'License :: OSI Approved :: GNU General Public License v3 or later (GPLv3+)',
53+
54+
# Specify the Python versions you support here. In particular, ensure
55+
# that you indicate whether you support Python 2, Python 3 or both.
56+
'Programming Language :: Python :: 2',
57+
'Programming Language :: Python :: 2.6',
58+
'Programming Language :: Python :: 2.7',
59+
'Programming Language :: Python :: 3',
60+
'Programming Language :: Python :: 3.3',
61+
'Programming Language :: Python :: 3.4',
62+
'Programming Language :: Python :: 3.5',
63+
],
64+
65+
# What does your project relate to?
66+
keywords='iot sensors networks',
67+
68+
# You can just specify the packages manually here if your project is
69+
# simple. Or you can use find_packages().
70+
packages=find_packages(exclude=['contrib', 'docs', 'tests']),
71+
72+
# Alternatively, if you want to distribute just a my_module.py, uncomment
73+
# this:
74+
# py_modules=["my_module"],
75+
76+
# List run-time dependencies here. These will be installed by pip when
77+
# your project is installed. For an analysis of "install_requires" vs pip's
78+
# requirements files see:
79+
# https://packaging.python.org/en/latest/requirements.html
80+
install_requires=['sanction'],
81+
82+
# List additional groups of dependencies here (e.g. development
83+
# dependencies). You can install these using the following syntax,
84+
# for example:
85+
# $ pip install -e .[dev,test]
86+
extras_require={
87+
'dev': ['check-manifest'],
88+
'test': ['coverage'],
89+
},
90+
91+
# If there are data files included in your packages that need to be
92+
# installed, specify them here. If using Python 2.6 or less, then these
93+
# have to be included in MANIFEST.in as well.
94+
package_data={},
95+
96+
# Although 'package_data' is the preferred approach, in some case you may
97+
# need to place data files outside of your packages. See:
98+
# http://docs.python.org/3.4/distutils/setupscript.html#installing-additional-files # noqa
99+
# In this case, 'data_file' will be installed into '<sys.prefix>/my_data'
100+
data_files=[],
101+
102+
# To provide executable scripts, use entry points in preference to the
103+
# "scripts" keyword. Entry points provide cross-platform support and allow
104+
# pip to create the appropriate form of executable for the target platform.
105+
entry_points={},
106+
)

0 commit comments

Comments
 (0)