Skip to content

Commit

Permalink
feat: Add the ability to proxy the requests to their respective sink (#3
Browse files Browse the repository at this point in the history
)
  • Loading branch information
lhw authored Jun 4, 2024
1 parent 35b5fa2 commit 252d37d
Show file tree
Hide file tree
Showing 4 changed files with 73 additions and 8 deletions.
7 changes: 3 additions & 4 deletions aiocloudweather/__main__.py
Original file line number Diff line number Diff line change
Expand Up @@ -28,9 +28,8 @@ async def my_handler(station: WeatherStation) -> None:
if value is None:
continue

print(f"{sensor.name}: {value.metric} ({value.metric_unit})")
print(f"{sensor.name}: {value.imperial} ({value.imperial_unit})")
print()
# print(f"{sensor.name}: {value.metric} ({value.metric_unit})")
# print(f"{sensor.name}: {value.imperial} ({value.imperial_unit})")

# print(f"{str(station)}")

Expand All @@ -49,7 +48,7 @@ def main() -> None:
sys.exit(1)

print(f"Firing up webserver to listen on port {sys.argv[1]}")
cloudweather_server = CloudWeatherListener(port=sys.argv[1])
cloudweather_server = CloudWeatherListener(port=sys.argv[1], proxy_enabled=True)

cloudweather_server.new_dataset_cb.append(my_handler)
try:
Expand Down
42 changes: 42 additions & 0 deletions aiocloudweather/proxy.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
"""Proxy for forwarding data to the CloudWeather APIs."""

from enum import Enum
from aiohttp import web
from dns_client.adapters.requests import DNSClientSession
import random
from urllib.parse import quote

import requests


class DataSink(Enum):
"""Data sinks for the CloudWeather API."""

WUNDERGROUND = "wunderground"
WEATHERCLOUD = "weathercloud"


class CloudWeatherProxy:
"""Proxy for forwarding data to the CloudWeather API."""

def __init__(self, dns_servers: list[str]):
self.session = DNSClientSession(host=random.choice(dns_servers))

async def forward_wunderground(self, request: web.Request) -> requests.Response:
"""Forward Wunderground data to the API."""
query_string = quote(request.query_string).replace("%20", "+")
url = f"https://rtupdate.wunderground.com/weatherstation/updateweatherstation.php?{query_string}"
return self.session.get(url)

async def forward_weathercloud(self, request: web.Request) -> web.Response:
"""Forward WeatherCloud data to the API."""
new_path = request.path[request.path.index("/v01/set") :]
url = f"https://api.weathercloud.net{new_path}"
return self.session.get(url)

async def forward(self, sink: DataSink, request: web.Request) -> requests.Response:
"""Forward data to the CloudWeather API."""
if sink == DataSink.WUNDERGROUND:
return await self.forward_wunderground(request)
if sink == DataSink.WEATHERCLOUD:
return await self.forward_weathercloud(request)
30 changes: 27 additions & 3 deletions aiocloudweather/server.py
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,8 @@

from aiohttp import web

from aiocloudweather.station import (
from .proxy import CloudWeatherProxy, DataSink
from .station import (
WundergroundRawSensor,
WeathercloudRawSensor,
WeatherStation,
Expand All @@ -23,11 +24,20 @@
class CloudWeatherListener:
"""CloudWeather Server API server."""

def __init__(self, port: int = _CLOUDWEATHER_LISTEN_PORT):
def __init__(
self, port: int = _CLOUDWEATHER_LISTEN_PORT, proxy_enabled=False, **kwargs: Any
):
"""Initialize CloudWeather Server."""
# API Constants
self.port: int = port

# Proxy functionality
self.proxy_enabled: bool = proxy_enabled
if self.proxy_enabled:
self.proxy = CloudWeatherProxy(
dns_servers=kwargs.get("dns_servers", ["9.9.9.9"])
)

# webserver
self.server: None | web.Server = None
self.runner: None | web.ServerRunner = None
Expand Down Expand Up @@ -71,7 +81,7 @@ async def process_wunderground(
async def process_weathercloud(self, segments: list[str]) -> WeatherStation:
"""Process WeatherCloud data."""

data = dict(zip(segments[::2], map(int, segments[1::2])))
data = dict(zip(segments[::2], segments[1::2]))
dfields = {
f.metadata["arg"]: f
for f in fields(WeathercloudRawSensor)
Expand All @@ -93,14 +103,17 @@ async def handler(self, request: web.BaseRequest) -> web.Response:

station_id: str = None
dataset: WeatherStation = None
sink: DataSink = None
if request.path.endswith("/weatherstation/updateweatherstation.php"):
dataset = await self.process_wunderground(request.query)
station_id = dataset.station_id
sink = DataSink.WUNDERGROUND
elif "/v01/set" in request.path:
dataset_path = request.path.split("/v01/set/", 1)[1]
path_segments = dataset_path.split("/")
dataset = await self.process_weathercloud(path_segments)
station_id = dataset.station_id
sink = DataSink.WEATHERCLOUD

if station_id not in self.stations:
_LOGGER.debug("Found new station: %s", station_id)
Expand All @@ -126,6 +139,17 @@ async def handler(self, request: web.BaseRequest) -> web.Response:
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("CloudWeather new dataset callback error: %s", err)

if self.proxy_enabled and sink is not None:
try:
response = await self.proxy.forward(sink, request)
_LOGGER.debug(
"CloudWeather proxy response[%d]: %s",
response.status_code,
response.text,
)
except Exception as err: # pylint: disable=broad-except
_LOGGER.warning("CloudWeather proxy error: %s", err)

self.last_values[station_id] = deepcopy(dataset)
return web.Response(text="OK")

Expand Down
2 changes: 1 addition & 1 deletion setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@
packages=find_packages(exclude=["tests.*", "tests"]),
package_data={"aiocoudweather": ["py.typed"]},
python_requires=">=3.12",
install_requires=["aiohttp>3"],
install_requires=["aiohttp>3", "requests>2", "dns-client>0.2"],
entry_points={
"console_scripts": ["cloudweather-testserver = aiocloudweather.__main__:main"]
},
Expand Down

0 comments on commit 252d37d

Please sign in to comment.