Telegram bot and swagger
This commit is contained in:
parent
b3db1ca78f
commit
f563489328
@ -157,3 +157,6 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
|
||||
# https://docs.djangoproject.com/en/5.2/ref/settings/#default-auto-field
|
||||
|
||||
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
|
||||
|
||||
TELEGRAM_BOT_TOKEN = os.environ['TELEGRAM_BOT_TOKEN']
|
||||
TELEGRAM_CHAT_ID = os.environ['TELEGRAM_CHAT_ID']
|
||||
@ -24,6 +24,6 @@ urlpatterns = [
|
||||
path('api/schema/', SpectacularAPIView.as_view(), name='schema'),
|
||||
path('swagger/', SpectacularSwaggerView.as_view(url_name='schema'), name='swagger-ui'),
|
||||
path('api/', include([
|
||||
path('', include('weather.urls')),
|
||||
path('weather/', include('weather.urls')),
|
||||
])),
|
||||
]
|
||||
|
||||
@ -3,7 +3,7 @@ from .models import WeatherStats
|
||||
|
||||
# Register your models here.
|
||||
class WeatherStatsAdmin(admin.ModelAdmin):
|
||||
list_display = ('temperature', 'humidityAir', 'humidityGround', 'light', 'created_at')
|
||||
list_display = ('temperature', 'humidity_air', 'humidity_ground', 'light', 'created_at')
|
||||
readonly_fields = ('created_at',)
|
||||
|
||||
admin.site.register(WeatherStats, WeatherStatsAdmin)
|
||||
@ -0,0 +1,23 @@
|
||||
# Generated by Django 5.2.8 on 2025-11-28 09:55
|
||||
|
||||
from django.db import migrations
|
||||
|
||||
|
||||
class Migration(migrations.Migration):
|
||||
|
||||
dependencies = [
|
||||
('weather', '0001_initial'),
|
||||
]
|
||||
|
||||
operations = [
|
||||
migrations.RenameField(
|
||||
model_name='weatherstats',
|
||||
old_name='humidityAir',
|
||||
new_name='humidity_air',
|
||||
),
|
||||
migrations.RenameField(
|
||||
model_name='weatherstats',
|
||||
old_name='humidityGround',
|
||||
new_name='humidity_ground',
|
||||
),
|
||||
]
|
||||
@ -2,8 +2,8 @@ from django.db import models
|
||||
|
||||
# Create your models here.
|
||||
class WeatherStats(models.Model):
|
||||
humidityAir = models.FloatField(default=0)
|
||||
humidityGround = models.FloatField(default=0)
|
||||
humidity_air = models.FloatField(default=0)
|
||||
humidity_ground = models.FloatField(default=0)
|
||||
temperature = models.FloatField(default=0)
|
||||
light = models.FloatField(default=0)
|
||||
created_at = models.DateTimeField(auto_now_add=True)
|
||||
|
||||
@ -6,4 +6,4 @@ class WeatherStatSerializer(serializers.ModelSerializer):
|
||||
|
||||
class Meta:
|
||||
model = WeatherStats
|
||||
fields = ('humidityAir', 'humidityGround', 'temperature', 'light', 'created_at')
|
||||
fields = ('humidity_air', 'humidity_ground', 'temperature', 'light', 'created_at')
|
||||
@ -1,6 +1,8 @@
|
||||
from django.urls import path
|
||||
from .views import CreateStatAPI
|
||||
from .views import *
|
||||
|
||||
urlpatterns = [
|
||||
path('stats/', CreateStatAPI.as_view())
|
||||
path('create/', CreateStatAPI.as_view()),
|
||||
path('last/', LastStatAPI.as_view()),
|
||||
path('history/', StatsHistoryAPI.as_view()),
|
||||
]
|
||||
|
||||
@ -1,11 +1,63 @@
|
||||
import os
|
||||
import requests
|
||||
from django.db.models import Avg
|
||||
from django.utils import timezone
|
||||
from datetime import timedelta
|
||||
|
||||
from rest_framework.pagination import PageNumberPagination
|
||||
from rest_framework.request import Request
|
||||
from rest_framework import permissions
|
||||
|
||||
from .models import WeatherStats
|
||||
from project.settings_context import TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID
|
||||
|
||||
class IsMikrokontroller(permissions.BasePermission):
|
||||
|
||||
def has_permission(self, request: Request, view):
|
||||
mikro_key = os.environ['MIKRO_SECRET_KEY']
|
||||
request_key = request.headers.get('X-Mikro-Key')
|
||||
|
||||
return request_key == mikro_key
|
||||
return request_key == mikro_key
|
||||
|
||||
class SmallPageNumberPagination(PageNumberPagination):
|
||||
page_size = 200
|
||||
page_size_query_param = 'page_size'
|
||||
max_page_size = 500
|
||||
|
||||
def format_value(val, unit="%"):
|
||||
return f"{val:.1f}{unit}" if val is not None else "—"
|
||||
|
||||
def send_telegram_stats():
|
||||
now = timezone.now()
|
||||
thirty_minutes_ago = now - timedelta(minutes=30)
|
||||
now_formatted = now.strftime("%d.%m.%Y %H:%M")
|
||||
|
||||
# Collect data
|
||||
averages = WeatherStats.objects.filter(
|
||||
created_at__gte=thirty_minutes_ago
|
||||
).aggregate(
|
||||
avg_humidity_air = Avg('humidity_air'),
|
||||
avg_humidity_ground = Avg('humidity_ground'),
|
||||
avg_temperature = Avg('temperature'),
|
||||
avg_light = Avg('light')
|
||||
)
|
||||
|
||||
message = f"""
|
||||
<b> Weather average stats for 30 mins ({now_formatted})</b>
|
||||
|
||||
<b>Humidity Air:</b> {format_value(averages['avg_humidity_air'])}
|
||||
<b>Humidity Ground:</b> {format_value(averages['avg_humidity_ground'])}
|
||||
<b>Humidity Temperature:</b> {format_value(averages['avg_temperature', '°C'])}
|
||||
<b>Light:</b> {format_value(averages['avg_light'])}
|
||||
"""
|
||||
|
||||
url = f"https://api.telegram.org/bot{TELEGRAM_BOT_TOKEN}/sendMessage"
|
||||
params = {
|
||||
"chat_id": TELEGRAM_CHAT_ID,
|
||||
"text": message,
|
||||
"parse_mode": "HTML"
|
||||
}
|
||||
|
||||
requests.get(url, params)
|
||||
|
||||
|
||||
@ -1,13 +1,21 @@
|
||||
from rest_framework.generics import CreateAPIView
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter
|
||||
from django.core.cache import cache
|
||||
from rest_framework.generics import CreateAPIView, ListAPIView
|
||||
from rest_framework.views import APIView
|
||||
from rest_framework.request import Request
|
||||
from rest_framework.response import Response
|
||||
from rest_framework import status
|
||||
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse
|
||||
|
||||
from .models import WeatherStats
|
||||
from .serializers import WeatherStatSerializer
|
||||
from .utils import IsMikrokontroller
|
||||
from .utils import IsMikrokontroller, PageNumberPagination
|
||||
|
||||
CACHE_KEY_WEATHER = 'weather_latest_data'
|
||||
CACHE_TIMEOUT = 30 # 30 seconds
|
||||
|
||||
# Create your views here.
|
||||
@extend_schema(tags=['Weather'],
|
||||
description="Call method by mikrocontroller to set new data (required MIKRO_SECRET_KEY with header X-Mikro-Key)",
|
||||
description="Call method by mikrocontroller to set new data (required MIKRO_SECRET_KEY with header X-Mikro-Key) with delay 30 seconds",
|
||||
parameters=[
|
||||
OpenApiParameter(
|
||||
name='X-Mikro-Key',
|
||||
@ -21,4 +29,45 @@ from .utils import IsMikrokontroller
|
||||
class CreateStatAPI(CreateAPIView):
|
||||
serializer_class = WeatherStatSerializer
|
||||
permission_classes = [ IsMikrokontroller ]
|
||||
queryset = WeatherStats.objects.all()
|
||||
queryset = WeatherStats.objects.all()
|
||||
|
||||
def perform_create(self, serializer: WeatherStatSerializer):
|
||||
new_data = serializer.validated_data
|
||||
cached_data = cache.get(CACHE_KEY_WEATHER)
|
||||
|
||||
if cached_data is None:
|
||||
serializer.save()
|
||||
|
||||
cache.set(CACHE_KEY_WEATHER, new_data, CACHE_TIMEOUT)
|
||||
|
||||
|
||||
@extend_schema(tags=['Weather'],
|
||||
description="Get latest stat by microcontroller. Can be used to get actual data now",
|
||||
responses={
|
||||
200: WeatherStatSerializer,
|
||||
404: OpenApiResponse(
|
||||
description="No latest data available from microcontroller",
|
||||
response={
|
||||
"message": "We don't have latest data from microcontroller"
|
||||
}
|
||||
)
|
||||
}
|
||||
)
|
||||
class LastStatAPI(APIView):
|
||||
def get(self, request: Request, format=None):
|
||||
cached_data = cache.get(CACHE_KEY_WEATHER)
|
||||
|
||||
if cached_data is None:
|
||||
return Response(
|
||||
{"message": "We don't have latest data from microcontroller. Maybe microcontroller is not connected"},
|
||||
status=status.HTTP_404_NOT_FOUND
|
||||
)
|
||||
|
||||
return Response(cached_data)
|
||||
|
||||
@extend_schema(tags=['Weather'], description="Get full history for graph")
|
||||
class StatsHistoryAPI(ListAPIView):
|
||||
serializer_class = WeatherStatSerializer
|
||||
queryset = WeatherStats.objects.order_by('-created_at')
|
||||
pagination_class = PageNumberPagination
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user