Import project
commit
abbaac347c
|
@ -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`
|
|
@ -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
|
|
@ -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()
|
||||
|
|
@ -0,0 +1,5 @@
|
|||
#!/bin/bash
|
||||
virtualenv ENV
|
||||
source ENV/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
|
@ -0,0 +1,4 @@
|
|||
PyYAML
|
||||
pymongo
|
||||
flask
|
||||
Flask-RESTful
|
|
@ -0,0 +1,2 @@
|
|||
#!/bin/bash
|
||||
uwsgi --ini /opt/level2/uwsgi.ini
|
|
@ -0,0 +1,8 @@
|
|||
[uwsgi]
|
||||
socket = :3031
|
||||
processes = 1
|
||||
module = api
|
||||
callable = app
|
||||
plugin = python3
|
||||
virtualenv = /opt/level2/ENV
|
||||
chdir = /opt/level2
|
|
@ -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
|
|
@ -0,0 +1,6 @@
|
|||
#!/bin/bash
|
||||
|
||||
virtualenv ENV
|
||||
source ENV/bin/activate
|
||||
|
||||
pip install -r requirements.txt
|
|
@ -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)
|
|
@ -0,0 +1,3 @@
|
|||
PyYAML
|
||||
mongoengine
|
||||
requests
|
|
@ -0,0 +1,3 @@
|
|||
#!/bin/bash
|
||||
source ENV/bin/activate
|
||||
python3 collect.py
|
|
@ -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
|
Loading…
Reference in New Issue