Merge branch 'develop' into 'master'

Develop

See merge request !47
This commit is contained in:
Jan Tuomi
2017-09-24 15:05:46 +03:00
4 changed files with 320 additions and 325 deletions
+34 -56
View File
@@ -3,74 +3,52 @@ import logging
import datetime
from collections import deque
MQTT_HOST = "mqtt.sik.party"
from django.conf import settings
TOPIC_TEMP = "sik/kiltahuone/kahvivaaka/temperature"
TOPIC_WEIGHT = "sik/kiltahuone/kahvivaaka/weight"
BREWING_DIFFERENTIAL_MIN = 60
# setting down the pan creates at least 800 g of weight
BREWING_DIFFERENTIAL_MAX = 180
WEIGHT_DEQUE_LENGTH = 20
weight_deque = deque(maxlen=WEIGHT_DEQUE_LENGTH)
latest = {
'last_brew': datetime.datetime.now()
}
def calc_averaged_weight():
if len(weight_deque) > 2:
half = []
for i in range(0, int(len(weight_deque) / 2)):
half.append(weight_deque[i])
return sum(half) / len(half)
else:
return 0
HOST = settings.MQTT_SETTINGS['HOST']
PORT = settings.MQTT_SETTINGS['PORT']
TOPICS = settings.MQTT_SETTINGS['TOPICS']
latest = {}
def on_connect(client, userdata, flags, rc):
logging.info('Subscribing to all topics on mqtt.sik.party.')
client.subscribe("#")
logging.info('Connected successfully to MQTT.')
logging.info('Subscribing to all topics on {}.'.format(HOST))
client.subscribe('sik/kiltahuone/kahvivaaka/#')
def update_latest(msg):
payload = msg.payload.decode('utf-8')
if msg.topic == TOPICS['WEIGHT']:
weight = float(payload)
latest['weight'] = weight
elif msg.topic == TOPICS['CUPS']:
cups = float(payload)
latest['cups'] = cups
elif msg.topic == TOPICS['BREWING']:
brewing = bool(int(payload))
latest['brewing'] = brewing
elif msg.topic == TOPICS['BREW_TIME']:
brew_time = datetime.datetime.fromtimestamp(float(payload))
latest['brew_time'] = brew_time
def on_message(client, userdata, msg):
if msg.topic == TOPIC_TEMP:
latest['temp'] = float(msg.payload.decode('utf-8'))
elif msg.topic == TOPIC_WEIGHT:
weight = float(msg.payload.decode('utf-8'))
# avoid showing erroneous data from an empty scale
if weight > 50: # g
weight_deque.appendleft(weight)
try:
update_latest(msg)
except Exception as ex:
logging.exception('Failed to parse MQTT payload.')
def on_disconnect(client, userdata, rc):
if rc != 0:
logging.warning("MQTT unexpectedly disconnected.")
logging.warning('MQTT unexpectedly disconnected.')
else:
client.loop_stop(force=False)
logging.warning("MQTT disconnected.")
logging.warning('MQTT disconnected.')
def get_latest():
if len(weight_deque) > 2:
first = weight_deque[0]
last = weight_deque[len(weight_deque) - 1]
diff = first - last # reverse order
if len(weight_deque) < WEIGHT_DEQUE_LENGTH:
brewing = False
else:
if BREWING_DIFFERENTIAL_MIN < diff < BREWING_DIFFERENTIAL_MAX:
brewing = True
latest['last_brew'] = datetime.datetime.now()
else:
brewing = False
latest['brewing'] = brewing
latest['weight'] = calc_averaged_weight()
return latest
@@ -81,9 +59,9 @@ client.on_disconnect = on_disconnect
try:
logging.info('Connecting to MQTT at {}...'.format(MQTT_HOST))
client.connect_async(MQTT_HOST, 1883, 60)
logging.info('Connected successfully to MQTT.')
logging.info('Connecting to MQTT at {}...'.format(HOST))
logging.info('If there is no confirmation, the MQTT connection has probably failed.')
client.connect_async(HOST, PORT, 60)
except Exception as ex:
logging.error(ex)
logging.error('Failed to connect to MQTT at {}'.format(MQTT_HOST))
logging.error('Failed to connect to MQTT at {}'.format(HOST))
+4 -23
View File
@@ -9,24 +9,6 @@ import logging
from django.conf import settings
def get_cups_from_weight(weight):
if not weight:
return 0
EMPTY = 820 # grams
FULL = 2000 # grams
cups = 10 * (weight - EMPTY) / (FULL - EMPTY)
cups = round(cups, 1)
if cups < 0.5:
cups = 0
if cups > 10:
cups = 10
# logging.debug("Coffee cups: {}, weight: {}".format(cups, weight))
return cups
def coffee_view(request):
logging.info('User navigated to coffee page!')
return render(request, 'coffee.html')
@@ -35,12 +17,11 @@ def coffee_view(request):
def cups_view(request):
now = datetime.datetime.now()
latest = get_latest()
cups = get_cups_from_weight(latest.get('weight'))
data = {
'date': now,
'cups': cups,
'temp': latest.get('temp'),
'last_brew': latest.get('last_brew'),
'brewing': latest.get('brewing')
'cups': latest.get('cups'),
'last_brew': latest.get('brew_time'),
'brewing': latest.get('brewing'),
'weight': latest.get('weight')
}
return JsonResponse(data)
+245
View File
@@ -0,0 +1,245 @@
import os
import logging
from os.path import expanduser
from django.utils.translation import ugettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IS_DOCKER = bool(os.getenv('IS_DOCKER', None))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
SESSION_SERIALIZER = 'django.contrib.sessions.serializers.PickleSerializer'
if not IS_DOCKER:
ALLOWED_HOSTS = []
else:
ALLOWED_HOSTS = ["*"]
# Logger level
LOGGERLEVEL = logging.DEBUG
LOGPATH = os.path.join(BASE_DIR, "logs", "debug.log")
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s: %(message)s'
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': LOGPATH,
'formatter': 'verbose',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'root': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': True,
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'WARNING',
'propagate': True,
},
},
}
# Application definition
INSTALLED_APPS = [
'modeltranslation', # has to be before admin for translation admin to work
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'webapp',
'members',
'infoscreen',
'coffee_scale',
'rest_framework',
'django_nose',
'bootstrap3',
'django_tables2',
'auditlog',
'phonenumber_field',
]
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
'--with-coverage',
'--cover-package=webapp,members,infoscreen',
'--exclude-dir={}'.format(os.path.join(BASE_DIR, 'members', 'migrations')),
'--exclude-dir={}'.format(os.path.join(BASE_DIR,
'infoscreen', 'migrations')),
'--exclude-dir={}'.format(os.path.join(BASE_DIR, 'webapp', 'migrations')),
]
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'auditlog.middleware.AuditlogMiddleware'
]
CORS_ORIGIN_ALLOW_ALL = True
ROOT_URLCONF = 'sikweb.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.template.context_processors.i18n',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.static',
'dealer.contrib.django.context_processor',
],
},
},
]
WSGI_APPLICATION = 'sikweb.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
if not IS_DOCKER:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'sik',
'USER': 'sik',
'PASSWORD': 'password123',
'HOST': 'localhost',
'PORT': '5432',
'TEST': {
'NAME': 'sik_test',
},
},
}
else:
logging.info('Using docker database configuration')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'postgres',
'USER': 'postgres',
'PASSWORD': 'postgres',
'HOST': 'db',
'PORT': '5432',
},
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.'
'UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.'
'MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.'
'CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.'
'NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.DjangoModelPermissions',
'rest_framework.permissions.IsAdminUser',
),
'DEFAULT_THROTTLE_CLASSES': (
'members.throttles.BurstRateThrottle',
'members.throttles.SustainedRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'burst': '60/min',
'sustained': '1000/day'
},
}
# Email settings (tested working with gmail)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGES = (
('fi', _('Finnish')),
('en', _('English')),
)
LANGUAGE_CODE = 'fi'
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
TIME_ZONE = 'Europe/Helsinki'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'django.contrib.staticfiles.finders.FileSystemFinder',
)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'global_static'),
)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
+37 -246
View File
@@ -11,264 +11,55 @@ For the full list of settings and their values, see
https://docs.djangoproject.com/en/1.9/ref/settings/
"""
import os
import logging
from os.path import expanduser
from django.utils.translation import ugettext_lazy as _
# Build paths inside the project like this: os.path.join(BASE_DIR, ...)
BASE_DIR = os.path.dirname(os.path.dirname(os.path.abspath(__file__)))
IS_DOCKER = bool(os.getenv('IS_DOCKER', None))
# Quick-start development settings - unsuitable for production
# See https://docs.djangoproject.com/en/1.9/howto/deployment/checklist/
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp('
from sikweb.base import *
# SECURITY WARNING: don't run with debug turned on in production!
DEBUG = True
if not IS_DOCKER:
ALLOWED_HOSTS = []
else:
ALLOWED_HOSTS = ["*"]
# SECURITY WARNING: keep the secret key used in production secret!
SECRET_KEY = '7p$85^4ibb^p4-=vs44b7!y0e-zemugze18@a#30&71=a8)dp('
# Logger level
# HSL API settings
HSL_USERHASH = 'YOUR HSL USERHASH HERE'
HSL_DEPARTURE_THRESHOLD = 8 # minutes
HSL_HURRY_THRESHOLD = 13 # minutes
LOGGERLEVEL = logging.DEBUG
LOGPATH = os.path.join(BASE_DIR, "logs", "debug.log")
LOGGING = {
'version': 1,
'disable_existing_loggers': False,
'formatters': {
'verbose': {
'format': '%(levelname)s %(asctime)s %(module)s: %(message)s'
},
},
'handlers': {
'file': {
'level': 'DEBUG',
'class': 'logging.FileHandler',
'filename': LOGPATH,
'formatter': 'verbose',
},
'console': {
'level': 'DEBUG',
'class': 'logging.StreamHandler',
'formatter': 'verbose',
},
},
'root': {
'handlers': ['file', 'console'],
'level': 'DEBUG',
'propagate': True,
},
'loggers': {
'django': {
'handlers': ['file', 'console'],
'level': 'WARNING',
'propagate': True,
},
},
# MQTT settings
MQTT_SETTINGS = {
'HOST': 'mqtt.sik.party',
'PORT': 1883,
'TOPICS': {
'BREW_TIME': 'sik/kiltahuone/kahvivaaka/brewtime',
'WEIGHT': 'sik/kiltahuone/kahvivaaka/weight',
'BREWING': 'sik/kiltahuone/kahvivaaka/brewing',
'CUPS': 'sik/kiltahuone/kahvivaaka/cups',
}
}
# ReCaptcha
# http://www.yaconiello.com/blog/integrating-google-recaptcha-to-django/
GOOGLE_RECAPTCHA_SITE_KEY = "YOUR-PUBLIC-KEY"
GOOGLE_RECAPTCHA_SECRET_KEY = "YOUR-PRIVATE-KEY"
# Application definition
INSTALLED_APPS = [
'modeltranslation', # has to be before admin for translation admin to work
'django.contrib.admin',
'django.contrib.auth',
'django.contrib.contenttypes',
'django.contrib.sessions',
'django.contrib.messages',
'django.contrib.staticfiles',
'corsheaders',
'webapp',
'members',
'infoscreen',
'coffee_scale',
'rest_framework',
'django_nose',
'bootstrap3',
'django_tables2',
'auditlog',
'phonenumber_field',
]
TEST_RUNNER = 'django_nose.NoseTestSuiteRunner'
NOSE_ARGS = [
'--with-coverage',
'--cover-package=webapp,members,infoscreen',
'--exclude-dir={}'.format(os.path.join(BASE_DIR, 'members', 'migrations')),
'--exclude-dir={}'.format(os.path.join(BASE_DIR,
'infoscreen', 'migrations')),
'--exclude-dir={}'.format(os.path.join(BASE_DIR, 'webapp', 'migrations')),
]
MIDDLEWARE_CLASSES = [
'django.middleware.security.SecurityMiddleware',
'django.contrib.sessions.middleware.SessionMiddleware',
'django.middleware.locale.LocaleMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
'django.middleware.csrf.CsrfViewMiddleware',
'django.contrib.auth.middleware.AuthenticationMiddleware',
'django.contrib.auth.middleware.SessionAuthenticationMiddleware',
'django.contrib.messages.middleware.MessageMiddleware',
'django.middleware.clickjacking.XFrameOptionsMiddleware',
'auditlog.middleware.AuditlogMiddleware'
]
CORS_ORIGIN_ALLOW_ALL = True
ROOT_URLCONF = 'sikweb.urls'
TEMPLATES = [
{
'BACKEND': 'django.template.backends.django.DjangoTemplates',
'DIRS': ['templates'],
'APP_DIRS': True,
'OPTIONS': {
'context_processors': [
'django.template.context_processors.debug',
'django.template.context_processors.request',
'django.template.context_processors.i18n',
'django.contrib.auth.context_processors.auth',
'django.contrib.messages.context_processors.messages',
'django.template.context_processors.static',
'dealer.contrib.django.context_processor',
],
},
},
]
WSGI_APPLICATION = 'sikweb.wsgi.application'
# Database
# https://docs.djangoproject.com/en/1.9/ref/settings/#databases
if not IS_DOCKER:
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'sik',
'USER': 'sik',
'PASSWORD': 'password123',
'HOST': 'localhost',
'PORT': '5432',
'TEST': {
'NAME': 'sik_test',
},
},
}
else:
logging.info('Using docker database configuration')
DATABASES = {
'default': {
'ENGINE': 'django.db.backends.postgresql_psycopg2',
'NAME': 'postgres',
'USER': 'postgres',
'PASSWORD': 'postgres',
'HOST': 'db',
'PORT': '5432',
},
}
# Password validation
# https://docs.djangoproject.com/en/1.9/ref/settings/#auth-password-validators
AUTH_PASSWORD_VALIDATORS = [
{
'NAME': 'django.contrib.auth.password_validation.'
'UserAttributeSimilarityValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.'
'MinimumLengthValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.'
'CommonPasswordValidator',
},
{
'NAME': 'django.contrib.auth.password_validation.'
'NumericPasswordValidator',
},
]
REST_FRAMEWORK = {
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.IsAuthenticated',
'rest_framework.permissions.DjangoModelPermissions',
'rest_framework.permissions.IsAdminUser',
),
'DEFAULT_THROTTLE_CLASSES': (
'members.throttles.BurstRateThrottle',
'members.throttles.SustainedRateThrottle'
),
'DEFAULT_THROTTLE_RATES': {
'burst': '60/min',
'sustained': '1000/day'
},
}
# Email settings (tested working with gmail)
EMAIL_BACKEND = 'django.core.mail.backends.smtp.EmailBackend'
EMAIL_USE_TLS = True
EMAIL_HOST = 'smtp.gmail.com'
EMAIL_PORT = 587
# Email settings (more settings in base.py)
EMAIL_HOST_USER = '<gmailtunnarisi>@gmail.com'
EMAIL_HOST_PASSWORD = '<gmail_passu>'
DEFAULT_EMAIL_FROM = 'SIK Viestintä <sikviestinta@gmail.com>'
ENABLE_AUTOMATIC_EMAILS = False
# ReCaptcha
# http://www.yaconiello.com/blog/integrating-google-recaptcha-to-django/
# Database settings
# Only uncomment if default settings in base.py are not ok
GOOGLE_RECAPTCHA_SITE_KEY = "YOUR-PUBLIC-KEY"
GOOGLE_RECAPTCHA_SECRET_KEY = "YOUR-PRIVATE-KEY"
# Internationalization
# https://docs.djangoproject.com/en/1.9/topics/i18n/
LANGUAGES = (
('fi', _('Finnish')),
('en', _('English')),
)
LANGUAGE_CODE = 'fi'
LOCALE_PATHS = (
os.path.join(BASE_DIR, 'locale'),
)
TIME_ZONE = 'Europe/Helsinki'
USE_I18N = True
USE_L10N = True
USE_TZ = True
# Static files (CSS, JavaScript, Images)
# https://docs.djangoproject.com/en/1.9/howto/static-files/
STATICFILES_FINDERS = (
'django.contrib.staticfiles.finders.AppDirectoriesFinder',
'django.contrib.staticfiles.finders.FileSystemFinder',
)
STATIC_URL = '/static/'
STATIC_ROOT = os.path.join(BASE_DIR, 'static')
STATICFILES_DIRS = (
os.path.join(BASE_DIR, 'global_static'),
)
MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
MEDIA_URL = '/media/'
HSL_USERHASH = 'YOUR HSL USERHASH HERE'
HSL_DEPARTURE_THRESHOLD = 8
HSL_HURRY_THRESHOLD = 13
# DATABASES = {
# 'default': {
# 'ENGINE': 'django.db.backends.postgresql_psycopg2',
# 'NAME': 'sik',
# 'USER': 'sik',
# 'PASSWORD': 'password123',
# 'HOST': 'localhost',
# 'PORT': '5432',
# 'TEST': {
# 'NAME': 'sik_test',
# },
# },
# }