Import project

master
defane 2015-08-08 16:24:42 +02:00
commit abbaac347c
13 changed files with 427 additions and 0 deletions

53
README.md Normal file
View File

@ -0,0 +1,53 @@
# Level2's sensors
----
## Installation
----
### Requirements
* docker
* docker-compose
* web server with uwsgi support
----
### Application configuration
1. Create mongodb data container: `docker create --name=sensors-mongodb-data mongo`
2. Set basic ACL on mongodb:
a. `docker start sensors-mongodb-data`
b. `docker exec -it sensors-mongodb-data mongo particule`
c. > `db.createUser({user: "sensors", pwd: "<password>", roles: [ "dbOwner" ]});`
d. > `exit`
e. `docker stop sensors-mongodb-data`
3. Create data container: `docker create --name=sensors-data -v /etc/l2-sensors debian`
4. Create configuration file in data container: `docker run -it --rm --volumes-from sensors-data busybox vi /etc/l2-sensors/conf.yml`
Example of conf.yml
devices:
- id: <ID>
variables:
- temperatureC
- humidity
- pressure
db:
mongo: {username: sensors, password: <password>}
token: <token>
### Reverse proxy configuration
Example of configuration for nginx
server {
listen 443 ssl;
server_name sensors.level2.lu;
location / {
include uwsgi_params;
uwsgi_pass 172.17.42.1:3031;
}
}
----
## Usage
* To start (in project root directory): `docker-compose up -d`

26
api/Dockerfile Normal file
View File

@ -0,0 +1,26 @@
FROM ubuntu:latest
RUN apt-get update && apt-get update -y
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
RUN locale-gen en_US en_US.UTF-8
RUN dpkg-reconfigure locales
RUN apt-get install -y python3-pip uwsgi uwsgi-plugin-python3
RUN pip3 install virtualenv
RUN useradd -d /opt/level2 -m level2
WORKDIR /opt/level2
COPY . /opt/level2/
USER level2
ENV HOME /opt/level2
ENV USER level2
RUN /opt/level2/build.sh
CMD /opt/level2/run.sh
EXPOSE 3031

222
api/api.py Normal file
View File

@ -0,0 +1,222 @@
import os
import yaml
import pymongo
import datetime
from flask import Flask, redirect
from flask_restful import Resource, Api, abort, reqparse
app = Flask(__name__)
api = Api(app)
config = None
with open("/etc/l2-sensors/conf.yml", 'r') as f:
config = yaml.load(f.read())
if config is None:
raise ValueError("Failed to load configuration.")
client = pymongo.MongoClient(
os.environ['DB_PORT_27017_TCP_ADDR'],
int(os.environ['DB_PORT_27017_TCP_PORT']),
)
client.particule.authenticate(config['db']['mongo']['username'], config['db']['mongo']['password'])
db = client.particule
@app.route("/")
def index():
return """
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="content-type" content="text/html; charset=UTF-8">
<title>Temperature, pressure and humidity at level2</title>
<script type='text/javascript' src='//code.jquery.com/jquery-1.9.1.js'></script>
<script src="//code.highcharts.com/stock/highstock.js"></script>
<script src="//code.highcharts.com/stock/modules/exporting.js"></script>
<script type='text/javascript'>//<![CDATA[
$(function () {
$.getJSON('/temperature', function (data) {
var newdata = []
for(var i in data['data']){
var date = new Date(data['data'][i]['date']);
newdata.push([date.getTime(), data['data'][i]['value']])
}
// Create the chart
$('#container').highcharts('StockChart', {
rangeSelector : {
selected : 1
},
title : {
text : 'Temperature °C'
},
series : [{
name : 'Temperature',
data : newdata,
tooltip: {
valueDecimals: 2
}
}]
});
});
});
$(function () {
$.getJSON('/pressure', function (data) {
var newdata = []
for(var i in data['data']){
var date = new Date(data['data'][i]['date']);
newdata.push([date.getTime(), data['data'][i]['value']])
}
// Create the chart
$('#containerPressure').highcharts('StockChart', {
rangeSelector : {
selected : 1
},
title : {
text : 'Pressure hPa'
},
series : [{
name : 'Pressure',
data : newdata,
tooltip: {
valueDecimals: 2
}
}]
});
});
});
$(function () {
$.getJSON('/humidity', function (data) {
var newdata = []
for(var i in data['data']){
var date = new Date(data['data'][i]['date']);
newdata.push([date.getTime(), data['data'][i]['value']])
}
// Create the chart
$('#containerHumidity').highcharts('StockChart', {
rangeSelector : {
selected : 1
},
title : {
text : 'Humidity %'
},
series : [{
name : 'Humidity',
data : newdata,
tooltip: {
valueDecimals: 0
}
}]
});
});
});
//]]>
</script>
</head>
<body>
<div id="container" style="height: 400px; min-width: 310px"></div>
<div id="containerPressure" style="height: 400px; min-width: 310px"></div>
<div id="containerHumidity" style="height: 400px; min-width: 310px"></div>
</body>
</html>
"""
parser = reqparse.RequestParser()
parser.add_argument('year', type=int, help='Year')
parser.add_argument('month', type=int, help='Month')
parser.add_argument('day', type=int, help='Day')
class GenericResource(Resource):
name = None
def _get_cursor(self, begin=None, end=None):
req = {"name": self.name}
if not isinstance(begin, (datetime.datetime, type(None))):
raise TypeError("begin must be datetime")
if not isinstance(end, (datetime.datetime, type(None))):
raise TypeError("end must be datetime")
if begin is not None and end is not None:
req['_date'] = { "$lte": end, "$gte": begin }
return db.measure.find(req).sort("_date", 1)
def value_filter(self, value):
return value
def get(self):
args = parser.parse_args()
begin, end = None, None
if args['year'] and args['month'] and args['day']:
try:
selected_date = datetime.datetime(args['year'], args['month'], args['day'])
begin = selected_date
end = begin + datetime.timedelta(1)
except Exception as e:
abort(400, message="Date incorrect")
cursor = self._get_cursor(begin, end)
if not cursor.count():
abort(404, message="No data found for this date")
data = [
{
"date":i['_date'].isoformat(),
"value": self.value_filter(i['result'])
} for i in cursor
]
return {"data": data}
class TemperatureResource(GenericResource):
name = "temperatureC"
def value_filter(self, value):
return round(value, 3)
class PressureResource(GenericResource):
name = "pressure"
def value_filter(self, value):
return round(value, 3)
class HumidityResource(GenericResource):
name = "humidity"
def value_filter(self, value):
return round(value)
api.add_resource(TemperatureResource, '/temperature')
api.add_resource(PressureResource, '/pressure')
api.add_resource(HumidityResource, '/humidity')
if __name__ == "__main__":
app.run()

5
api/build.sh Executable file
View File

@ -0,0 +1,5 @@
#!/bin/bash
virtualenv ENV
source ENV/bin/activate
pip install -r requirements.txt

4
api/requirements.txt Normal file
View File

@ -0,0 +1,4 @@
PyYAML
pymongo
flask
Flask-RESTful

2
api/run.sh Executable file
View File

@ -0,0 +1,2 @@
#!/bin/bash
uwsgi --ini /opt/level2/uwsgi.ini

8
api/uwsgi.ini Normal file
View File

@ -0,0 +1,8 @@
[uwsgi]
socket = :3031
processes = 1
module = api
callable = app
plugin = python3
virtualenv = /opt/level2/ENV
chdir = /opt/level2

24
collect/Dockerfile Normal file
View File

@ -0,0 +1,24 @@
FROM ubuntu:latest
RUN apt-get update && apt-get update -y
ENV LC_ALL en_US.UTF-8
ENV LANG en_US.UTF-8
RUN locale-gen en_US en_US.UTF-8
RUN dpkg-reconfigure locales
RUN apt-get install -y python3-pip
RUN pip3 install virtualenv
RUN useradd -d /opt/level2 -m level2
WORKDIR /opt/level2
COPY . /opt/level2/
USER level2
ENV HOME /opt/level2
ENV USER level2
RUN /opt/level2/build.sh
CMD /opt/level2/run.sh

6
collect/build.sh Executable file
View File

@ -0,0 +1,6 @@
#!/bin/bash
virtualenv ENV
source ENV/bin/activate
pip install -r requirements.txt

52
collect/collect.py Normal file
View File

@ -0,0 +1,52 @@
#!/usr/bin/env python3
import os
import time
import yaml
import mongoengine
import datetime
import requests
base_url = "https://api.particle.io/v1/devices/"
config = None
with open("/etc/l2-sensors/conf.yml", 'r') as f:
config = yaml.load(f.read())
if config is None:
raise ValueError("Failed to load configuration.")
mongoengine.connect(
'particule',
host=os.environ['DB_PORT_27017_TCP_ADDR'],
port=int(os.environ['DB_PORT_27017_TCP_PORT']),
username=config['db']['mongo']['username'],
password=config['db']['mongo']['password']
)
class Measure(mongoengine.DynamicDocument):
_date = mongoengine.DateTimeField(default=datetime.datetime.now)
meta = {
'indexes': [
{'fields': ['name', '_date']}
],
}
while True:
try:
for device in config['devices']:
for variable in device['variables']:
res = requests.get("%s%s/%s?access_token=%s" %
(
base_url,
device['id'],
variable,
config['token']
)
)
Measure(**res.json()).save()
except (KeyboardInterrupt, SystemExit):
break
except Exception as e:
print(e)
finally:
time.sleep(600)

3
collect/requirements.txt Normal file
View File

@ -0,0 +1,3 @@
PyYAML
mongoengine
requests

3
collect/run.sh Executable file
View File

@ -0,0 +1,3 @@
#!/bin/bash
source ENV/bin/activate
python3 collect.py

19
docker-compose.yml Normal file
View File

@ -0,0 +1,19 @@
api:
build: api/
ports:
- "172.17.42.1:3031:3031"
links:
- mongodb:db
volumes_from:
- sensors-data
collect:
build: collect/
links:
- mongodb:db
volumes_from:
- sensors-data
mongodb:
image: mongo
command: mongod --auth
volumes_from:
- sensors-mongodb-data