openmeteo api weather

This commit is contained in:
stepan323446 2025-12-05 10:35:10 +01:00
parent 35e32c996c
commit c5e0f5a9a9
7 changed files with 106 additions and 26 deletions

4
project/serializers.py Normal file
View File

@ -0,0 +1,4 @@
from rest_framework import serializers
class MessageSerializer(serializers.Serializer):
message = serializers.CharField(max_length=255)

View File

@ -159,7 +159,4 @@ MEDIA_ROOT = os.path.join(BASE_DIR, 'media')
DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField' DEFAULT_AUTO_FIELD = 'django.db.models.BigAutoField'
TELEGRAM_BOT_TOKEN = os.environ['TELEGRAM_BOT_TOKEN'] TELEGRAM_BOT_TOKEN = os.environ['TELEGRAM_BOT_TOKEN']
TELEGRAM_CHAT_ID = os.environ['TELEGRAM_CHAT_ID'] TELEGRAM_CHAT_ID = os.environ['TELEGRAM_CHAT_ID']
WEATHER_LATITUDE = os.environ['WEATHER_LATITUDE']
WEATHER_LONGITUDE = os.environ['WEATHER_LONGITUDE']

View File

@ -16,6 +16,7 @@ jh2==5.0.10
jsonschema==4.25.1 jsonschema==4.25.1
jsonschema-specifications==2025.9.1 jsonschema-specifications==2025.9.1
niquests==3.15.2 niquests==3.15.2
numpy==2.3.5
openmeteo_requests==1.7.4 openmeteo_requests==1.7.4
openmeteo_sdk==1.23.0 openmeteo_sdk==1.23.0
python-dotenv==1.2.1 python-dotenv==1.2.1

View File

@ -6,4 +6,15 @@ class WeatherStatSerializer(serializers.ModelSerializer):
class Meta: class Meta:
model = WeatherStats model = WeatherStats
fields = ('humidity_air', 'humidity_ground', 'temperature', 'light', 'created_at') 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()

View File

@ -4,5 +4,6 @@ from .views import *
urlpatterns = [ urlpatterns = [
path('create/', CreateStatAPI.as_view()), path('create/', CreateStatAPI.as_view()),
path('last/', LastStatAPI.as_view()), path('last/', LastStatAPI.as_view()),
path('history/', StatsHistoryAPI.as_view()), path('history/', StatsHistoryAPI.as_view()),
path('open-meteo/', OpenMeteoWeatherAPI.as_view()),
] ]

View File

@ -1,6 +1,7 @@
import os import os
import requests import requests
import openmeteo_requests import openmeteo_requests
from datetime import datetime
from django.db.models import Avg from django.db.models import Avg
from django.utils import timezone from django.utils import timezone
from datetime import timedelta from datetime import timedelta
@ -10,7 +11,7 @@ from rest_framework.request import Request
from rest_framework import permissions from rest_framework import permissions
from .models import WeatherStats 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): class IsMikrokontroller(permissions.BasePermission):
@ -61,7 +62,7 @@ def send_telegram_stats():
requests.get(url, params) requests.get(url, params)
def get_weather_api_data(): def get_weather_api_data(latitude, longitude):
now = timezone.now() now = timezone.now()
openmeteo = openmeteo_requests.Client() openmeteo = openmeteo_requests.Client()
start_date = now + timedelta(days=1) start_date = now + timedelta(days=1)
@ -72,9 +73,16 @@ def get_weather_api_data():
url = "https://api.open-meteo.com/v1/forecast" url = "https://api.open-meteo.com/v1/forecast"
params = { params = {
"latitude": WEATHER_LATITUDE, "latitude": latitude,
"longitude": WEATHER_LONGITUDE, "longitude": longitude,
"hourly": ["temperature_2m", "relative_humidity_2m", "precipitation_probability", "precipitation", "wind_speed_10m"], "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, "start_date": start_date_formatted,
"end_date": end_date_formatted, "end_date": end_date_formatted,
} }
@ -83,5 +91,32 @@ def get_weather_api_data():
response = responses[0] response = responses[0]
days = response.Daily() days = response.Daily()
dates = days.Time()
return response 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

View File

@ -1,4 +1,6 @@
import time import time
from django.utils.decorators import method_decorator
from django.views.decorators.cache import cache_page
from django.core.cache import cache from django.core.cache import cache
from rest_framework.generics import CreateAPIView, ListAPIView from rest_framework.generics import CreateAPIView, ListAPIView
from rest_framework.views import APIView from rest_framework.views import APIView
@ -7,15 +9,18 @@ from rest_framework.response import Response
from rest_framework import status from rest_framework import status
from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse from drf_spectacular.utils import extend_schema, OpenApiParameter, OpenApiResponse
from project.serializers import MessageSerializer
from .models import WeatherStats from .models import WeatherStats
from .serializers import WeatherStatSerializer from .serializers import WeatherStatSerializer, OpenWeatherAPISerializer
from .utils import IsMikrokontroller, PageNumberPagination from .utils import IsMikrokontroller, PageNumberPagination, get_weather_api_data
CACHE_SAVE_CONTROL = 'stats_latest_saved_control' CACHE_SAVE_CONTROL = 'stats_latest_saved_control'
CACHE_KEY_STATS = 'stats_latest_data' CACHE_KEY_STATS = 'stats_latest_data'
CACHE_KEY_WEATHER = 'weather_latest_data'
CACHE_TIMEOUT = 30 # 30 seconds CACHE_TIMEOUT = 30 # 30 seconds
CACHE_KEY_WEATHER = 'weather_latest_data'
CACHE_TIMEOUT_WEATHER = 60 * 5 # 5 minutes
# Create your views here. # Create your views here.
@extend_schema(tags=['Weather'], @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", 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", description="Get latest stat by microcontroller. Can be used to get actual data now",
responses={ responses={
200: WeatherStatSerializer, 200: WeatherStatSerializer,
404: OpenApiResponse( 404: MessageSerializer
description="No latest data available from microcontroller",
response={
"message": "We don't have latest data from microcontroller"
}
)
} }
) )
class LastStatAPI(APIView): class LastStatAPI(APIView):
@ -76,9 +76,40 @@ class StatsHistoryAPI(ListAPIView):
queryset = WeatherStats.objects.order_by('-created_at') queryset = WeatherStats.objects.order_by('-created_at')
pagination_class = PageNumberPagination 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) @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)