Skip to content

Commit

Permalink
Add Brewfather support (Closes #370, Closes #277)
Browse files Browse the repository at this point in the history
  • Loading branch information
thorrak committed Nov 29, 2019
1 parent ffb95c7 commit 264dc8d
Show file tree
Hide file tree
Showing 10 changed files with 357 additions and 11 deletions.
5 changes: 4 additions & 1 deletion external_push/admin.py
@@ -1,6 +1,6 @@
from django.contrib import admin

from external_push.models import GenericPushTarget, BrewersFriendPushTarget
from external_push.models import GenericPushTarget, BrewersFriendPushTarget, BrewfatherPushTarget


@admin.register(GenericPushTarget)
Expand All @@ -11,3 +11,6 @@ class GenericPushTargetAdmin(admin.ModelAdmin):
class BrewersFriendPushTargetAdmin(admin.ModelAdmin):
list_display = ('gravity_sensor_to_push', 'status', 'push_frequency')

@admin.register(BrewfatherPushTarget)
class BrewfatherPushTargetAdmin(admin.ModelAdmin):
list_display = ('gravity_sensor_to_push', 'status', 'push_frequency')
7 changes: 5 additions & 2 deletions external_push/forms.py
@@ -1,6 +1,6 @@
from django import forms

from external_push.models import GenericPushTarget, BrewersFriendPushTarget
from external_push.models import GenericPushTarget, BrewersFriendPushTarget, BrewfatherPushTarget
from django.core import validators
import fermentrack_django.settings as settings

Expand All @@ -20,4 +20,7 @@ class Meta:
fields = ['gravity_sensor_to_push', 'push_frequency', 'api_key']



class BrewfatherPushTargetModelForm(ModelForm):
class Meta:
model = BrewfatherPushTarget
fields = ['gravity_sensor_to_push', 'push_frequency', 'logging_url']
38 changes: 38 additions & 0 deletions external_push/migrations/0004_Brewfather_Support.py
@@ -0,0 +1,38 @@
# -*- coding: utf-8 -*-
# Generated by Django 1.11.11 on 2019-11-29 21:31
from __future__ import unicode_literals

from django.db import migrations, models
import django.db.models.deletion


class Migration(migrations.Migration):

dependencies = [
('gravity', '0004_BrewersFriend_Support'),
('external_push', '0003_BrewersFriend_Support'),
]

operations = [
migrations.CreateModel(
name='BrewfatherPushTarget',
fields=[
('id', models.AutoField(auto_created=True, primary_key=True, serialize=False, verbose_name='ID')),
('status', models.CharField(choices=[('active', 'Active'), ('disabled', 'Disabled'), ('error', 'Error')], default='active', help_text='Status of this push target', max_length=24)),
('push_frequency', models.IntegerField(choices=[(901, '15 minutes'), (1801, '30 minutes'), (3601, '1 hour')], default=900, help_text='How often to push data to the target')),
('logging_url', models.CharField(default='', help_text='Brewfather Logging URL', max_length=256)),
('error_text', models.TextField(blank=True, default='', help_text='The error (if any) encountered on the last push attempt', null=True)),
('last_triggered', models.DateTimeField(auto_now_add=True, help_text='The last time we pushed data to this target')),
('gravity_sensor_to_push', models.ForeignKey(help_text='Gravity Sensor to push (create one push target per sensor to push)', on_delete=django.db.models.deletion.CASCADE, related_name='brewfather_push_target', to='gravity.GravitySensor')),
],
options={
'verbose_name': 'Brewfather Push Target',
'verbose_name_plural': 'Brewfather Push Targets',
},
),
migrations.AlterField(
model_name='brewersfriendpushtarget',
name='gravity_sensor_to_push',
field=models.ForeignKey(help_text='Gravity Sensor to push (create one push target per sensor to push)', on_delete=django.db.models.deletion.CASCADE, related_name='brewers_friend_push_target', to='gravity.GravitySensor'),
),
]
90 changes: 89 additions & 1 deletion external_push/models.py
Expand Up @@ -281,7 +281,7 @@ class Meta:
help_text="How often to push data to the target")
api_key = models.CharField(max_length=256, help_text="Brewers Friend API Key", default="")

gravity_sensor_to_push = models.ForeignKey(to=GravitySensor, related_name="push_target", on_delete=models.CASCADE,
gravity_sensor_to_push = models.ForeignKey(to=GravitySensor, related_name="brewers_friend_push_target", on_delete=models.CASCADE,
help_text="Gravity Sensor to push (create one push target per "
"sensor to push)")

Expand Down Expand Up @@ -336,3 +336,91 @@ def send_data(self):

r = requests.post(brewers_friend_url, data=json_data, headers=headers)
return True # TODO - Check if the post actually succeeded & react accordingly



class BrewfatherPushTarget(models.Model):
class Meta:
verbose_name = "Brewfather Push Target"
verbose_name_plural = "Brewfather Push Targets"

STATUS_ACTIVE = 'active'
STATUS_DISABLED = 'disabled'
STATUS_ERROR = 'error'

STATUS_CHOICES = (
(STATUS_ACTIVE, 'Active'),
(STATUS_DISABLED, 'Disabled'),
(STATUS_ERROR, 'Error'),
)

PUSH_FREQUENCY_CHOICES = (
(60 * 15 + 1, '15 minutes'),
(60 * 30 + 1, '30 minutes'),
(60 * 60 + 1, '1 hour'),
)

status = models.CharField(max_length=24, help_text="Status of this push target", choices=STATUS_CHOICES,
default=STATUS_ACTIVE)
push_frequency = models.IntegerField(choices=PUSH_FREQUENCY_CHOICES, default=60 * 15,
help_text="How often to push data to the target")
logging_url = models.CharField(max_length=256, help_text="Brewfather Logging URL", default="")

gravity_sensor_to_push = models.ForeignKey(to=GravitySensor, related_name="brewfather_push_target", on_delete=models.CASCADE,
help_text="Gravity Sensor to push (create one push target per "
"sensor to push)")

error_text = models.TextField(blank=True, null=True, default="", help_text="The error (if any) encountered on the "
"last push attempt")

last_triggered = models.DateTimeField(help_text="The last time we pushed data to this target", auto_now_add=True)

# I'm on the fence as to whether or not to test when to trigger by selecting everything from the database and doing
# (last_triggered + push_frequency) < now, or to actually create a "trigger_next_at" field.
# trigger_next_at = models.DateTimeField(default=timezone.now, help_text="When to next trigger a push")

def __str__(self):
return self.gravity_sensor_to_push.name

def data_to_push(self):
# For Brewfather, we're just cascading a single gravity sensor downstream to the app
to_send = {'report_source': "Fermentrack", 'name': self.gravity_sensor_to_push.name}

# TODO - Add beer name to what is pushed

latest_log_point = self.gravity_sensor_to_push.retrieve_latest_point()

if latest_log_point is None: # If there isn't an available log point, return nothing
return {}

# For now, if we can't get a latest log point, let's default to just not sending anything.
if latest_log_point.gravity != 0.0:
to_send['gravity'] = float(latest_log_point.gravity)
to_send['gravity_unit'] = "G"
else:
return {} # Also return nothing if there isn't an available gravity

# For now all gravity sensors have temp info, but just in case
if latest_log_point.temp is not None:
to_send['temp'] = float(latest_log_point.temp)
to_send['temp_unit'] = latest_log_point.temp_format

# TODO - Add linked BrewPi temps if we have them

string_to_send = json.dumps(to_send)

# We've got the data (in a json'ed string) - lets send it
return string_to_send

def send_data(self):
# self.data_to_push() returns a JSON-encoded string which we will push directly out
json_data = self.data_to_push()

if len(json_data) <= 2:
# There was no data to push - do nothing.
return False

headers = {'Content-Type': 'application/json'}

r = requests.post(self.logging_url, data=json_data, headers=headers)
return True # TODO - Check if the post actually succeeded & react accordingly
30 changes: 28 additions & 2 deletions external_push/tasks.py
Expand Up @@ -2,7 +2,7 @@
from __future__ import absolute_import, unicode_literals
from huey import crontab
from huey.contrib.djhuey import periodic_task, task, db_periodic_task, db_task
from external_push.models import GenericPushTarget, BrewersFriendPushTarget
from external_push.models import GenericPushTarget, BrewersFriendPushTarget, BrewfatherPushTarget

import datetime, pytz, time
from django.utils import timezone
Expand Down Expand Up @@ -32,12 +32,27 @@ def brewers_friend_push_target_push(target_id):

return None

@db_task()
def brewfather_push_target_push(target_id):
try:
push_target = BrewfatherPushTarget.objects.get(id=target_id)
except:
# TODO - Replace with ObjNotFound
return None

push_target.send_data()

return None


# TODO - At some point write a validation function that will allow us to trigger more often than every minute
@db_periodic_task(crontab(minute="*"))
def dispatch_push_tasks():
generic_push_targets = GenericPushTarget.objects.filter(status=GenericPushTarget.STATUS_ACTIVE).all()
brewers_friend_push_targets = BrewersFriendPushTarget.objects.filter(status=GenericPushTarget.STATUS_ACTIVE).all()
brewers_friend_push_targets = BrewersFriendPushTarget.objects.filter(status=BrewersFriendPushTarget.STATUS_ACTIVE).all()
brewfather_push_targets = BrewfatherPushTarget.objects.filter(status=BrewfatherPushTarget.STATUS_ACTIVE).all()

# Run through the list of generic push targets and trigger a (future) data send for each
for target in generic_push_targets:
if timezone.now() >= (target.last_triggered + datetime.timedelta(seconds=target.push_frequency)):
target.last_triggered = timezone.now()
Expand All @@ -46,6 +61,7 @@ def dispatch_push_tasks():
# Queue the generic_push_target_push task (going to do it asynchronously)
generic_push_target_push(target.id)

# Run through the list of Brewer's Friend push targets and trigger a (future) data send for each
for target in brewers_friend_push_targets:
if timezone.now() >= (target.last_triggered + datetime.timedelta(seconds=target.push_frequency)):
target.last_triggered = timezone.now()
Expand All @@ -54,4 +70,14 @@ def dispatch_push_tasks():
# Queue the generic_push_target_push task (going to do it asynchronously)
brewers_friend_push_target_push(target.id)

# Run through the list of Brewfather push targets and trigger a (future) data send for each
for target in brewfather_push_targets:
if timezone.now() >= (target.last_triggered + datetime.timedelta(seconds=target.push_frequency)):
target.last_triggered = timezone.now()
target.save()

# Queue the generic_push_target_push task (going to do it asynchronously)
brewfather_push_target_push(target.id)


return None
@@ -0,0 +1,41 @@
{% extends "sitewide/flat_ui_template.html" %}
{% load custom_tags %}


{% block title %}Add Brewfather Push Target{% endblock %}

{% block content %}

<h1 class="page-header">Add Brewfather Push Target</h1>
{% if form.errors %}
<div class="text-danger">Please correct the error {{ form.errors }} below.</div>
{% endif %}
<p>
<form action="{% url "external_push_brewfather_target_add" %}" class="form-horizontal" method="post">
{% csrf_token %}

<div id="pushTargetSection">
<h3 class="form-header">Push Target Settings</h3>
{% form_generic form.push_frequency %}
{% form_generic form.logging_url %}
{% form_generic form.gravity_sensor_to_push %}
</div>

<input type="submit" value="Add Push Target" class="btn btn-primary" />
</form>
</p>


{% endblock %}

{% block scripts %}
<script>

$(function () {
$('[data-toggle="tooltip"]').tooltip()
})

</script>

{% endblock %}

@@ -0,0 +1,46 @@
{% extends "sitewide/flat_ui_template.html" %}
{% load custom_tags %}


{% block title %}Brewfather Push Target {{ push_target }}{% endblock %}

{% block content %}

<h1 class="page-header">{{ push_target }}</h1>
{% if form.errors %}
<div class="text-danger">Please correct the error {{ form.errors }} below.</div>
{% endif %}
<p>
<form action="{% url "external_push_brewfather_view" push_target.id %}" class="form-horizontal" method="post">
{% csrf_token %}

<div id="pushTargetSection">
<h3 class="form-header">Push Target Settings</h3>
{% form_generic form.push_frequency %}
{% form_generic form.logging_url %}
{% form_generic form.gravity_sensor_to_push %}
</div>

<input type="submit" value="Update Push Target" class="btn btn-primary" />
</form>
</p>


<p>
<a href="{% url 'external_push_brewfather_delete' push_target.id %}" class="btn btn-large btn-lg btn-danger">Delete Push Target</a>
</p>


{% endblock %}

{% block scripts %}
<script>

$(function () {
$('[data-toggle="tooltip"]').tooltip()
})

</script>

{% endblock %}

Expand Up @@ -25,17 +25,34 @@ <h4>Generic Push Targets</h4>

{# List the Brewer's Friend push targets next #}
{% if brewers_friend_push_targets.count > 0 %}
<h4>Generic Push Targets</h4>
<h4>Brewer's Friend Push Targets</h4>
<ul class="list-group list-group-flush">
{% for push_target in brewers_friend_push_targets %}
<!-- GenericPushTarget Line -->
<!-- BrewersFriendPushTarget Line -->
<li class="list-group-item">
<div class="row">
<div class="col-sm-12 col-md-6"><a href="{% url "external_push_brewers_friend_view" push_target.id %}">{{ push_target }}</a></div>
<div class="col-sm-2 col-md-2">{{ push_target.status }}</div> {# TODO - Make this display an error if applicable #}
</div>
</li>
<!-- End GenericPushTarget Line -->
<!-- End BrewersFriendPushTarget Line -->
{% endfor %}
</ul>
{% endif %}

{# Then list the Brewfather push targets #}
{% if brewfather_push_targets.count > 0 %}
<h4>Brewfather Push Targets</h4>
<ul class="list-group list-group-flush">
{% for push_target in brewfather_push_targets %}
<!-- BrewfatherPushTarget Line -->
<li class="list-group-item">
<div class="row">
<div class="col-sm-12 col-md-6"><a href="{% url "external_push_brewfather_view" push_target.id %}">{{ push_target }}</a></div>
<div class="col-sm-2 col-md-2">{{ push_target.status }}</div> {# TODO - Make this display an error if applicable #}
</div>
</li>
<!-- End BrewfatherPushTarget Line -->
{% endfor %}
</ul>
{% endif %}
Expand All @@ -44,4 +61,5 @@ <h4>Generic Push Targets</h4>
<p>
<a href="{% url 'external_push_generic_target_add' %}" class="btn btn-large btn-lg btn-primary">Add Generic Push Target</a>
<a href="{% url 'external_push_brewers_friend_target_add' %}" class="btn btn-large btn-lg btn-primary">Add Brewer's Friend Push Target</a>
<a href="{% url 'external_push_brewfather_target_add' %}" class="btn btn-large btn-lg btn-primary">Add Brewfather Push Target</a>
</p>
4 changes: 4 additions & 0 deletions external_push/urls.py
Expand Up @@ -20,4 +20,8 @@
url(r'^push/brewersfriend/view/(?P<push_target_id>[0-9]{1,20})/$', external_push.views.external_push_brewers_friend_view, name='external_push_brewers_friend_view'),
url(r'^push/brewersfriend/delete/(?P<push_target_id>[0-9]{1,20})/$', external_push.views.external_push_brewers_friend_delete, name='external_push_brewers_friend_delete'),

url(r'^push/brewfather/add/$', external_push.views.external_push_brewfather_target_add, name='external_push_brewfather_target_add'),
url(r'^push/brewfather/view/(?P<push_target_id>[0-9]{1,20})/$', external_push.views.external_push_brewfather_view, name='external_push_brewfather_view'),
url(r'^push/brewfather/delete/(?P<push_target_id>[0-9]{1,20})/$', external_push.views.external_push_brewfather_delete, name='external_push_brewfather_delete'),

]

0 comments on commit 264dc8d

Please sign in to comment.