commit abbaac347c35990a3d87d89e4d8327abe0cf0688 Author: defane Date: Sat Aug 8 16:24:42 2015 +0200 Import project diff --git a/README.md b/README.md new file mode 100644 index 0000000..288f320 --- /dev/null +++ b/README.md @@ -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: "", 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: + variables: + - temperatureC + - humidity + - pressure + db: + mongo: {username: sensors, password: } + 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` diff --git a/api/Dockerfile b/api/Dockerfile new file mode 100644 index 0000000..8322acd --- /dev/null +++ b/api/Dockerfile @@ -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 diff --git a/api/api.py b/api/api.py new file mode 100644 index 0000000..fca8d1a --- /dev/null +++ b/api/api.py @@ -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 """ + + + + + Temperature, pressure and humidity at level2 + + + + + + +
+
+
+ + +""" + +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() + diff --git a/api/build.sh b/api/build.sh new file mode 100755 index 0000000..7d77067 --- /dev/null +++ b/api/build.sh @@ -0,0 +1,5 @@ +#!/bin/bash +virtualenv ENV +source ENV/bin/activate + +pip install -r requirements.txt diff --git a/api/requirements.txt b/api/requirements.txt new file mode 100644 index 0000000..2aa808d --- /dev/null +++ b/api/requirements.txt @@ -0,0 +1,4 @@ +PyYAML +pymongo +flask +Flask-RESTful diff --git a/api/run.sh b/api/run.sh new file mode 100755 index 0000000..d89b73f --- /dev/null +++ b/api/run.sh @@ -0,0 +1,2 @@ +#!/bin/bash +uwsgi --ini /opt/level2/uwsgi.ini diff --git a/api/uwsgi.ini b/api/uwsgi.ini new file mode 100644 index 0000000..1de6327 --- /dev/null +++ b/api/uwsgi.ini @@ -0,0 +1,8 @@ +[uwsgi] +socket = :3031 +processes = 1 +module = api +callable = app +plugin = python3 +virtualenv = /opt/level2/ENV +chdir = /opt/level2 diff --git a/collect/Dockerfile b/collect/Dockerfile new file mode 100644 index 0000000..d26a97a --- /dev/null +++ b/collect/Dockerfile @@ -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 diff --git a/collect/build.sh b/collect/build.sh new file mode 100755 index 0000000..ddef402 --- /dev/null +++ b/collect/build.sh @@ -0,0 +1,6 @@ +#!/bin/bash + +virtualenv ENV +source ENV/bin/activate + +pip install -r requirements.txt diff --git a/collect/collect.py b/collect/collect.py new file mode 100644 index 0000000..b15cee6 --- /dev/null +++ b/collect/collect.py @@ -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) diff --git a/collect/requirements.txt b/collect/requirements.txt new file mode 100644 index 0000000..987704b --- /dev/null +++ b/collect/requirements.txt @@ -0,0 +1,3 @@ +PyYAML +mongoengine +requests diff --git a/collect/run.sh b/collect/run.sh new file mode 100755 index 0000000..8bf97ff --- /dev/null +++ b/collect/run.sh @@ -0,0 +1,3 @@ +#!/bin/bash +source ENV/bin/activate +python3 collect.py diff --git a/docker-compose.yml b/docker-compose.yml new file mode 100644 index 0000000..677757f --- /dev/null +++ b/docker-compose.yml @@ -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