From c5e0f5a9a964e86490e4e0c005e0b15202d5ee8d Mon Sep 17 00:00:00 2001 From: stepan323446 Date: Fri, 5 Dec 2025 10:35:10 +0100 Subject: [PATCH] openmeteo api weather --- project/serializers.py | 4 +++ project/settings/base.py | 5 +--- requirements.txt | 1 + weather/serializers.py | 13 ++++++++- weather/urls.py | 3 +- weather/utils.py | 47 ++++++++++++++++++++++++++++---- weather/views.py | 59 ++++++++++++++++++++++++++++++---------- 7 files changed, 106 insertions(+), 26 deletions(-) create mode 100644 project/serializers.py diff --git a/project/serializers.py b/project/serializers.py new file mode 100644 index 0000000..1f8d8e8 --- /dev/null +++ b/project/serializers.py @@ -0,0 +1,4 @@ +from rest_framework import serializers + +class MessageSerializer(serializers.Serializer): + message = serializers.CharField(max_length=255) \ No newline at end of file diff --git a/project/settings/base.py b/project/settings/base.py index 365591e..d337904 100644 --- a/project/settings/base.py +++ b/project/settings/base.py @@ -159,7 +159,4 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media') DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' TELEGRAM_BOT_TOKEN = os.environ['TELEGRAM_BOT_TOKEN'] -TELEGRAM_CHAT_ID = os.environ['TELEGRAM_CHAT_ID'] - -WEATHER_LATITUDE = os.environ['WEATHER_LATITUDE'] -WEATHER_LONGITUDE = os.environ['WEATHER_LONGITUDE'] \ No newline at end of file +TELEGRAM_CHAT_ID = os.environ['TELEGRAM_CHAT_ID'] \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 3449829..6470b8c 100644 --- a/requirements.txt +++ b/requirements.txt @@ -16,6 +16,7 @@ jh2==5.0.10 jsonschema==4.25.1 jsonschema-specifications==2025.9.1 niquests==3.15.2 +numpy==2.3.5 openmeteo_requests==1.7.4 openmeteo_sdk==1.23.0 python-dotenv==1.2.1 diff --git a/weather/serializers.py b/weather/serializers.py index a9caf96..cb985ca 100644 --- a/weather/serializers.py +++ b/weather/serializers.py @@ -6,4 +6,15 @@ class WeatherStatSerializer(serializers.ModelSerializer): class Meta: model = WeatherStats - fields = ('humidity_air', 'humidity_ground', 'temperature', 'light', 'created_at') \ No newline at end of file + fields = ('humidity_air', 'humidity_ground', 'temperature', 'light', 'created_at') + + + + +class OpenWeatherAPISerializer(serializers.Serializer): + date = serializers.DateField() + temp_nigth = serializers.FloatField() + temp_day = serializers.FloatField() + humidity = serializers.FloatField() + wind_speed = serializers.FloatField() + precip_probability = serializers.FloatField() \ No newline at end of file diff --git a/weather/urls.py b/weather/urls.py index a5afb5a..1ff7783 100644 --- a/weather/urls.py +++ b/weather/urls.py @@ -4,5 +4,6 @@ from .views import * urlpatterns = [ path('create/', CreateStatAPI.as_view()), path('last/', LastStatAPI.as_view()), - path('history/', StatsHistoryAPI.as_view()), + path('history/', StatsHistoryAPI.as_view()), + path('open-meteo/', OpenMeteoWeatherAPI.as_view()), ] diff --git a/weather/utils.py b/weather/utils.py index afa32a7..f176fe9 100644 --- a/weather/utils.py +++ b/weather/utils.py @@ -1,6 +1,7 @@ import os import requests import openmeteo_requests +from datetime import datetime from django.db.models import Avg from django.utils import timezone from datetime import timedelta @@ -10,7 +11,7 @@ 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, WEATHER_LATITUDE, WEATHER_LONGITUDE +from project.settings_context import TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID class IsMikrokontroller(permissions.BasePermission): @@ -61,7 +62,7 @@ def send_telegram_stats(): requests.get(url, params) -def get_weather_api_data(): +def get_weather_api_data(latitude, longitude): now = timezone.now() openmeteo = openmeteo_requests.Client() start_date = now + timedelta(days=1) @@ -72,9 +73,16 @@ def get_weather_api_data(): url = "https://api.open-meteo.com/v1/forecast" params = { - "latitude": WEATHER_LATITUDE, - "longitude": WEATHER_LONGITUDE, - "hourly": ["temperature_2m", "relative_humidity_2m", "precipitation_probability", "precipitation", "wind_speed_10m"], + "latitude": latitude, + "longitude": longitude, + "daily": [ + "temperature_2m_min", + "temperature_2m_max", + "wind_speed_10m_max", + "precipitation_probability_max", + "relative_humidity_2m_mean" + ], + "timezone": "auto", "start_date": start_date_formatted, "end_date": end_date_formatted, } @@ -83,5 +91,32 @@ def get_weather_api_data(): response = responses[0] days = response.Daily() + dates = days.Time() - return response \ No newline at end of file + temp_min = days.Variables(0).ValuesAsNumpy() + temp_max = days.Variables(1).ValuesAsNumpy() + wind_max = days.Variables(2).ValuesAsNumpy() + precip_prob_max = days.Variables(3).ValuesAsNumpy() + humidity_mean = days.Variables(4).ValuesAsNumpy() + + result = [] + + count = len(days.Variables(0).ValuesAsNumpy()) + base_time = days.Time() + + for i in range(count): + timestamp = base_time + i * 86400 + + local_dt = datetime.fromtimestamp(timestamp) + + day = { + "date": local_dt.strftime("%Y-%m-%d"), + "temp_night": float(temp_min[i]), + "temp_day": float(temp_max[i]), + "humidity": float(humidity_mean[i]), + "wind_speed": float(wind_max[i]), + "precip_probability": float(precip_prob_max[i]) + } + result.append(day) + + return result \ No newline at end of file diff --git a/weather/views.py b/weather/views.py index b0aae60..fd83336 100644 --- a/weather/views.py +++ b/weather/views.py @@ -1,4 +1,6 @@ import time +from django.utils.decorators import method_decorator +from django.views.decorators.cache import cache_page from django.core.cache import cache from rest_framework.generics import CreateAPIView, ListAPIView from rest_framework.views import APIView @@ -7,15 +9,18 @@ from rest_framework.response import Response from rest_framework import status from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse +from project.serializers import MessageSerializer from .models import WeatherStats -from .serializers import WeatherStatSerializer -from .utils import IsMikrokontroller, PageNumberPagination +from .serializers import WeatherStatSerializer, OpenWeatherAPISerializer +from .utils import IsMikrokontroller, PageNumberPagination, get_weather_api_data CACHE_SAVE_CONTROL = 'stats_latest_saved_control' CACHE_KEY_STATS = 'stats_latest_data' -CACHE_KEY_WEATHER = 'weather_latest_data' CACHE_TIMEOUT = 30 # 30 seconds +CACHE_KEY_WEATHER = 'weather_latest_data' +CACHE_TIMEOUT_WEATHER = 60 * 5 # 5 minutes + # 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) with delay 30 seconds", @@ -50,12 +55,7 @@ class CreateStatAPI(CreateAPIView): 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" - } - ) + 404: MessageSerializer } ) class LastStatAPI(APIView): @@ -76,9 +76,40 @@ class StatsHistoryAPI(ListAPIView): queryset = WeatherStats.objects.order_by('-created_at') pagination_class = PageNumberPagination -class WeatherAPI(APIView): - def get(self, request: Request, format=None): - cached_data = cache.get(CACHE_KEY_WEATHER) - if cached_data: - return Response(cached_data) \ No newline at end of file + +@method_decorator(cache_page(CACHE_TIMEOUT_WEATHER), name='dispatch') +class OpenMeteoWeatherAPI(APIView): + + @extend_schema(tags=['Weather'], + description="Weather prediction using OpenMeteo API data based on latitude and longitude for 7 days", + parameters=[ + OpenApiParameter( + name='lat', + type=float, + location=OpenApiParameter.QUERY, + description='Latitude', + required=True + ), + OpenApiParameter( + name='long', + type=float, + location=OpenApiParameter.QUERY, + description='Longitude', + required=True + ) + ], + responses={ + 200: OpenWeatherAPISerializer(many=True), + 400: MessageSerializer + } + ) + def get(self, request: Request, format=None): + latitude = request.query_params.get('lat') + longitude = request.query_params.get('long') + + if not latitude or not longitude: + return Response("No latitude or longitude data", status=status.HTTP_400_BAD_REQUEST) + + weather_stats = get_weather_api_data(latitude, longitude) + return Response(weather_stats) \ No newline at end of file