Telegram bot and swagger

This commit is contained in:
stepan323446 2025-11-28 11:08:53 +01:00
parent b3db1ca78f
commit f563489328
9 changed files with 142 additions and 13 deletions

View File

@ -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']

View File

@ -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')),
])),
]

View File

@ -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)

View File

@ -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',
),
]

View File

@ -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)

View File

@ -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')

View File

@ -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()),
]

View File

@ -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)

View File

@ -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