Skip to content

Commit

Permalink
remote sensors with api
Browse files Browse the repository at this point in the history
marcelb98 committed May 20, 2019
1 parent a25155b commit 2f6ee21
Showing 6 changed files with 182 additions and 6 deletions.
62 changes: 59 additions & 3 deletions app.py
Original file line number Diff line number Diff line change
@@ -1,8 +1,9 @@
import datetime
import hashlib
import json
from functools import wraps

from flask import Flask, render_template, redirect, url_for, request, flash
from flask import Flask, render_template, redirect, url_for, request, flash, abort
from flask_navigation import Navigation
from sqlalchemy import and_, or_

@@ -35,8 +36,8 @@ def create_app():
def gen_nav():
nav.Bar('main', [
nav.Item('Dashboard', 'dashboard'),
nav.Item('Sensors', 'index', items=[nav.Item(s.name, 'sensor', {'sensorID': s.id}) for s in get_sensors()]+
[nav.Item('+ new', 'new_sensor')]
nav.Item('Sensors', 'index', items=[nav.Item(s.name, 'sensor', {'sensorID': s.id}) for s in get_sensors()] +
[nav.Item('+ new', 'new_sensor'), nav.Item('+ new remote', 'new_remote_sensor')]
),
nav.Item('Relays', 'index', items=[nav.Item(r.name, 'relay', {'relayID': r.id}) for r in get_relays()] +
[nav.Item('+ new', 'new_relay')]
@@ -100,13 +101,18 @@ def sensor(sensorID):
@app.route('/sensor/<int:sensorID>/delete')
@with_navigation
def del_sensor(sensorID):
sensor = model.Sensor.query.get(sensorID)
model.SensorValue.query.filter_by(sensor_id=sensorID).delete()
model.Condition_sensorCompare.query.filter( or_(model.Condition_sensorCompare.sensor1==sensorID,
model.Condition_sensorCompare.sensor2==sensorID)).delete()
model.Condition_sensorDiffCompare.query.filter(or_(model.Condition_sensorDiffCompare.sensor1 == sensorID,
model.Condition_sensorDiffCompare.sensor2 == sensorID)).delete()
model.Condition_valueCompare.query.filter_by(sensor=sensorID).delete()

if sensor.is_remote:
id = sensor.address1w[4:]
model.RemoteSensor.query.filter_by(id=id).delete()

model.Sensor.query.filter_by(id=sensorID).delete()
model.db.session.commit()
flash('Deletion successfull.')
@@ -127,6 +133,22 @@ def new_sensor():

return render_template('new_sensor.html', form=form)

@app.route('/sensor/new_remote', methods=['GET', 'POST'])
@with_navigation
def new_remote_sensor():
form = forms.NewRemoteSensorForm(request.form)

if request.method == 'POST' and form.validate():
rem_sensor = model.RemoteSensor()
model.db.session.add(rem_sensor)
model.db.session.flush()
sensor = model.Sensor('rem-{}'.format(rem_sensor.id), form.name.data)
model.db.session.add(sensor)
model.db.session.commit()
flash('Sensor created successfully.')

return render_template('new_remote_sensor.html', form=form)

@app.route('/relay/<int:relayID>/', methods=['GET', 'POST'])
@with_navigation
def relay(relayID):
@@ -346,6 +368,40 @@ def new_user():

return render_template('new_user.html', form=form)

@app.route('/api/remotesensor/<int:sensorID>', methods=['GET'])
def api_remote_sensor_new_value(sensorID):
sensor = model.Sensor.query.get(sensorID)

signature = request.args.get("signature","")
value = request.args.get("value","")

if sensor.is_remote:
addr = sensor.address1w[4:]
remote_sensor = model.RemoteSensor.query.get(addr)

# check if value is float
try:
float(value)
except ValueError:
return abort(400)

# check signature
s = remote_sensor.key + value
s = hashlib.sha256(s.encode()).hexdigest()

if s != signature:
# signature was wrong
return abort(403)

# save value
sv = model.SensorValue(sensor, float(value))
model.db.session.add(sv)
model.db.session.commit()

return "OK"
else:
return abort(400)

@app.route('/logout/')
def logout():
return "logout"
10 changes: 9 additions & 1 deletion forms.py
Original file line number Diff line number Diff line change
@@ -19,6 +19,14 @@ def validate_address1w(form, field):
if sensors is not None:
raise ValidationError("Sensor is already configured.")

class NewRemoteSensorForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(message="No name given")])

def validate_name(form, field):
sensors = Sensor.query.filter_by(name=field.data).first()
if sensors is not None:
raise ValidationError("Name already in use.")

class NewRelayForm(FlaskForm):
name = StringField('Name', validators=[DataRequired(message="No name given")])
port = SelectField('Port', validators=[DataRequired(message="No port specified")], coerce=int)
@@ -83,4 +91,4 @@ class CreateUserForm(FlaskForm):
def validate_name(form, field):
u = User.query.filter_by(username=field.data).first()
if u is not None:
raise ValidationError("Username already in use.")
raise ValidationError("Username already in use.")
28 changes: 28 additions & 0 deletions migrations/versions/453b00e0ca8e_remote_sensors.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
"""remote sensors
Revision ID: 453b00e0ca8e
Revises: 3cf7480569d1
Create Date: 2019-05-20 19:37:40.689172
"""
from alembic import op
import sqlalchemy as sa


# revision identifiers, used by Alembic.
revision = '453b00e0ca8e'
down_revision = '3cf7480569d1'
branch_labels = None
depends_on = None


def upgrade():
op.create_table('remotesensor',
sa.Column('id', sa.Integer(), nullable=False),
sa.Column('key', sa.String(), nullable=False),
sa.PrimaryKeyConstraint('id')
)


def downgrade():
op.drop_table('remotesensor')
34 changes: 32 additions & 2 deletions model.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import datetime
import random
import string

from flask_sqlalchemy import SQLAlchemy
from sqlalchemy.ext.hybrid import hybrid_property
@@ -59,6 +61,15 @@ def check_password(self, password):
def __repr__(self):
return '<User {}>'.format(self.username)

class RemoteSensor(db.Model):
__tablename__ = 'remotesensor'
id = db.Column(db.Integer, primary_key=True)
key = db.Column(db.String, nullable=False)

def __init__(self):
# After creating this remote sensor, a new Sensor('rem-{RemoteSensor.id}', '{name}') has to be created
self.key = ''.join(random.choice(string.ascii_letters + string.digits) for i in range(15))

class Sensor(db.Model):
__tablename__ = 'sensor'
id = db.Column(db.Integer, primary_key=True)
@@ -69,9 +80,28 @@ def __init__(self, address1w, name):
self.address1w = address1w
self.name = name

@hybrid_property
def is_remote(self):
return self.address1w.startswith('rem-')

@hybrid_property
def remoteSensor(self):
# returns object of remote sensor, if this sensor is a remote sensor, otherwise None
if self.is_remote:
id = self.address1w[4:]
return RemoteSensor.query.get(id)
else:
return None

@hybrid_property
def value(self):
return get_sensor_value(self.address1w)
if self.is_remote is True:
# sensor is a remote sensor, get last known value from db
value = SensorValue.query.filter_by(sensor_id=self.id).order_by(SensorValue.time.desc()).first()
return value.value if value is not None else False
else:
# sensor is connected 1w-sensor
return get_sensor_value(self.address1w)

class SensorValue(db.Model):
__tablename__ = 'sensorValue'
@@ -322,4 +352,4 @@ def __str__(self):
@hybrid_property
def fulfilled(self):
time = datetime.datetime.now().time()
return self.start_time <= time <= self.end_time
return self.start_time <= time <= self.end_time
37 changes: 37 additions & 0 deletions templates/new_remote_sensor.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
{% extends "_base.html" %}
{% set title='New sensor' %}
{% set heading='Configure new sensor' %}

{% block breadcumb %}
<ol class="breadcrumb">
<li>Home</li>
<li>Sensors</li>
<li class="active">New remote</li>
</ol>
{% endblock %}

{% block content %}
<p>
For remote sensors it is possible to push new temperature values with an HTTP API.
</p>
<form action="" method="post">
{{ form.csrf_token }}
<div class="row">
<div class="col-md-2">
{{ form.name.label }}
{% if form.name.errors %}<br><span class="text-danger">{{ form.name.errors[0] }}</span>{% endif %}
</div>
<div class="col-md-4">
{{ form.name(class_="form-control") }}
</div>
</div>
<div class="row">
<div class="col-md-2">
</div>
<div class="col-md-4">
<button type="submit" class="btn btn-primary">Create</button>
</div>
</div>
</form>

{% endblock %}
17 changes: 17 additions & 0 deletions templates/sensor.html
Original file line number Diff line number Diff line change
@@ -58,6 +58,23 @@
</div>
</div>

{% if sensor.is_remote %}
<div class="row">
<div class="col-md-12">
<p>
This sensor is a remote sensor. Values are not meassured on this device but received with API calls.
</p>
<p>
<b>API Credentials:</b><br>
ID: <tt>{{ sensor.id }}</tt><br>
Key: <tt>{{ sensor.remoteSensor.key }}</tt><br>
Send HTTP-GET-Requests to: <tt>{{ url_for('api_remote_sensor_new_value',sensorID=sensor.id) }}?value={value}&signature={signature}</tt><br>
where {value} is the current temperature in &deg;C as a float and signature is the SHA256 hash of the value concatenated with the key.
</p>
</div>
</div>
{% endif %}

{% endblock %}
{% block extraJS %}
<script type="text/javascript">

0 comments on commit 2f6ee21

Please sign in to comment.