Skip to content

Commit 2ce274f

Browse files
authored
Merge pull request #90 from TheFinalJoke/add_nws
finished the weather matrix refactoring
2 parents c8d8cba + d59d45e commit 2ce274f

File tree

6 files changed

+286
-5
lines changed

6 files changed

+286
-5
lines changed

lib/weather/normal.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
from typing import Dict, Tuple
33
from datetime import datetime, timedelta
44
from lib.weather.openweather.weather import OpenWeatherApi
5+
from lib.weather.weathergov.nws import NWSApi
56
from lib.run import Caller
67

78
class NormalizedWeather():
@@ -107,5 +108,6 @@ async def run_weather(self):
107108
open_weather = OpenWeatherApi(self.config)
108109
result = await open_weather.run()
109110
else:
110-
result = ""
111+
nws = NWSApi(self.config)
112+
result = await nws.run()
111113
return NormalizedWeather(result)

lib/weather/weatherbase.py

Lines changed: 21 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,25 @@ class APIWeather(Enum):
77
OPENWEATHER = 1
88
# National Weather Service
99
NWS = 2
10-
10+
11+
class WindDirection(Enum):
12+
S = 180
13+
N = 0
14+
W = 270
15+
E = 90
16+
NE = 45
17+
NW = 315
18+
NNE = 30
19+
ENE = 75
20+
ESE = 105
21+
SE = 135
22+
SSE = 165
23+
SSW = 210
24+
SW = 225
25+
WSW = 255
26+
WNW = 285
27+
NNW = 345
28+
1129
@dataclass(repr=True)
1230
class WeatherIcon:
1331
condition: str
@@ -55,4 +73,5 @@ class Weather():
5573
location: Tuple[float, float]
5674
location_name: str
5775
current: CurrentWeather
58-
dayforcast: DayForcast
76+
dayforcast: DayForcast
77+

lib/weather/weathergov/__init__.py

Whitespace-only changes.

lib/weather/weathergov/nws.py

Lines changed: 246 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,246 @@
1+
#!/usr/bin/env python3
2+
import asyncio
3+
import lib.weather.weatherbase as base
4+
from lib.weather.weather_icon import weather_icon_mapping
5+
from lib.asynclib import make_async
6+
from lib.run import Runner, Caller
7+
from datetime import datetime, timedelta
8+
from typing import Tuple, Dict, List
9+
from suntime import Sun
10+
11+
class NWSApi(Runner):
12+
13+
def __init__(self, config) -> None:
14+
super().__init__(config)
15+
self.weather = self.config['weather']
16+
17+
async def parse_args(self) -> str:
18+
"""
19+
Check if zipcode or city in config file
20+
"""
21+
22+
return await self.url_builder()
23+
24+
25+
async def get_long_and_lat(self, location: str=None, zipcode: int=None) -> Tuple:
26+
"""
27+
Searches for Longitude and latitude for Given City
28+
"""
29+
self.logger.debug("Getting Lat and Long")
30+
try:
31+
if location:
32+
self.logger.debug("Computing Longitude and Latitude")
33+
response = await self.get_data(url)
34+
lon = response.get('coord').get('lon')
35+
lat = response.get('coord').get('lat')
36+
return lon, lat
37+
else:
38+
raise Exception("Zip Code not Supported")
39+
except Exception as e:
40+
self.logger.critical(e)
41+
sys.exit("No City Found")
42+
43+
@make_async
44+
def get_current_location(self) -> Dict[str, str]:
45+
url = 'http://ipinfo.io/json'
46+
response = self.run_non_async_request(url)
47+
return response.json()
48+
49+
async def url_builder(self):
50+
"""
51+
Builds Url to poll the Api
52+
"""
53+
self.logger.debug("Building Weather url...")
54+
ip_json: Dict[str, str] = await self.get_current_location()
55+
lon, lat = ip_json['loc'].split(',')[1], ip_json['loc'].split(',')[0]
56+
url = f"https://api.weather.gov/points/{lat},{lon}"
57+
return url
58+
59+
async def run(self) -> Dict:
60+
self.logger.info("Running Api for Weather")
61+
args = await self.parse_args()
62+
main_json = await self.get_data(args)
63+
observation_url = f'https://api.weather.gov/stations/{main_json["properties"]["radarStation"]}/observations/latest'
64+
tasks = {
65+
'forcast': asyncio.create_task(self.get_data(main_json['properties']['forecast'])),
66+
'hourly': asyncio.create_task(self.get_data(main_json['properties']['forecastHourly'])),
67+
'observations': asyncio.create_task(self.get_data(observation_url))
68+
}
69+
await asyncio.gather(*tasks.values())
70+
count = 0
71+
while count <= 4:
72+
if not all([('status' in tasks['hourly'].result()), ('status' in tasks['forcast'].result())]):
73+
break
74+
count += 1
75+
tasks['hourly'] = asyncio.create_task(self.get_data(main_json['properties']['forecastHourly']))
76+
tasks['forcast'] = asyncio.create_task(self.get_data(main_json['properties']['forecast']))
77+
await asyncio.gather(tasks['hourly'], tasks['forcast'])
78+
api_data = {'main_json': main_json, 'forcast': tasks['forcast'].result(), 'hourly': tasks['hourly'].result(), 'observe': tasks['observations'].result()}
79+
return NWSTransform(api_data)
80+
81+
class NWSTransform(Caller):
82+
83+
def __init__(self, api: Dict) -> None:
84+
super().__init__()
85+
self.api = api
86+
self.api_json = api
87+
self._api_caller = base.APIWeather.NWS
88+
self._observation = self.api_json['observe']
89+
self._late_observation = self._observation
90+
self._lat_long = (self.api['main_json']['geometry']['coordinates'][1], self.api['main_json']['geometry']['coordinates'][0])
91+
self._place = self.api_json['main_json']['properties']['relativeLocation']['properties']['city']
92+
self._current = self.api_json['hourly']['properties']['periods'][0]
93+
self._weather = self.api_json['hourly']['properties']['periods']
94+
self._conditions = self._current['shortForecast']
95+
self._temp = int((self._late_observation['properties']['temperature']['value'] * 1.8) + 32)
96+
# There is no Feels like
97+
self._feels_like = self._temp
98+
self._daily = self.api_json['forcast']['properties']['periods'][0]
99+
# Have to figure out how to get the temp mina nd max with
100+
self._min_temp = min(self.determine_max_and_min_temps())
101+
self._max_temp = max(self.determine_max_and_min_temps())
102+
self._humidity = int(self._late_observation['properties']['relativeHumidity']['value'])
103+
self._wind_speed = int(self._late_observation['properties']['windSpeed']['value'] / 1.609344)
104+
self._wind_deg = self._late_observation['properties']['windDirection']['value']
105+
self._time = datetime.now()
106+
self._sunrise = self.gen_rise_and_set()[0]
107+
self._sunset = self.gen_rise_and_set()[1]
108+
self._pop = 0
109+
self._uv = None
110+
111+
def __repr__(self) -> str:
112+
attrs = [
113+
f"name={self._place}",
114+
f"current={json.dumps(self._current, indent=2)}",
115+
f"weather={json.dumps(self._weather, indent=2)}",
116+
f"conditions={self._conditions}",
117+
f"weather_icon={self._weather_icon}",
118+
f"temp={self._temp}",
119+
f"feels_like={self._feels_like}",
120+
f"daily={json.dumps(self._daily, indent=2)}",
121+
f"min_temp={self._min_temp}",
122+
f"max_temp={self._max_temp}",
123+
f"humidity={self._humidity}",
124+
f"wind_speed={self._wind_speed}",
125+
f"wind_deg={self._wind_deg}",
126+
f"time={self._time}",
127+
f"sunrise={self._sunrise}",
128+
f"sunset={self._sunset}",
129+
f"precipitation={self._pop}",
130+
f"uv={self._uv}"
131+
]
132+
joined_attrs = ',\n'.join(attrs)
133+
return f"Weather(\n{joined_attrs})"
134+
135+
@property
136+
def get_icon(self):
137+
# Have to get the icon
138+
condition: int = self._weather[0]['shortForecast'].lower()
139+
if any(s in condition.lower() for s in ("sunny", "clear", 'sun')):
140+
# Sunny
141+
if self._sunset > datetime.now():
142+
owm_icon = weather_icon_mapping[0]
143+
else:
144+
owm_icon = weather_icon_mapping[48]
145+
elif any(s in condition.lower() for s in ('rain', 'storm', 'thunderstorm ')):
146+
owm_icon = weather_icon_mapping[9]
147+
elif 'snow' in condition:
148+
owm_icon = weather_icon_mapping[13]
149+
elif any(s in condition.lower() for s in ('cloudy', 'cloud')):
150+
owm_icon = weather_icon_ampping[7]
151+
else:
152+
owm_icon = weather_icon_mapping[0]
153+
return owm_icon
154+
155+
def determine_max_and_min_temps(self) -> List[int]:
156+
return [entry['temperature'] for entry in self._weather if datetime.now().date() == datetime.fromisoformat(entry['startTime']).date()]
157+
158+
def gen_rise_and_set(self):
159+
lat, lng = self._lat_long
160+
tz = datetime.now().date()
161+
sun = Sun(lat, lng)
162+
sun_rise = sun.get_local_sunrise_time(tz)
163+
sun_set = sun.get_local_sunset_time(tz)
164+
return sun_rise, sun_set
165+
@property
166+
def get_api(self):
167+
return self._api_caller
168+
169+
@property
170+
def get_lat_long(self) -> Tuple[float, float]:
171+
return self._lat_long
172+
173+
@property
174+
def get_wind_speed(self) -> int:
175+
return self._wind_speed
176+
177+
@property
178+
def get_daily(self) -> Dict[str, str]:
179+
return self._daily
180+
181+
@property
182+
def get_wind_deg(self) -> int:
183+
return self._wind_deg
184+
185+
@property
186+
def get_precipitation(self) -> int:
187+
return self._pop
188+
189+
@property
190+
def get_uv(self) -> int:
191+
return self._uv
192+
193+
@property
194+
def get_place(self) -> str:
195+
return self._place
196+
197+
@property
198+
def get_weather(self) -> Dict[str, str]:
199+
return self._weather
200+
201+
@property
202+
def get_conditions(self) -> str:
203+
return self._conditions
204+
205+
@property
206+
def get_weather_icon(self) -> str:
207+
return self._weather_icon
208+
209+
@property
210+
def get_temp(self) -> int:
211+
return self._temp
212+
213+
@property
214+
def get_feels_like(self) -> int:
215+
return self._feels_like
216+
217+
@property
218+
def get_min_temp(self) -> int:
219+
return self._min_temp
220+
221+
@property
222+
def get_max_temp(self) -> int:
223+
return self._max_temp
224+
225+
@property
226+
def get_humidity(self) -> None:
227+
return self._humidity
228+
229+
@property
230+
def get_wind(self) -> Dict:
231+
return self._wind
232+
233+
@property
234+
def get_time(self) -> datetime:
235+
return self._time
236+
237+
@property
238+
def get_sunrise(self) -> datetime:
239+
return self._sunrise
240+
241+
@property
242+
def get_sunset(self) -> datetime:
243+
return self._sunset
244+
245+
def calculate_duration_of_daylight(self) -> timedelta:
246+
return self._sunset - self._time

matrix/weathermatrix.py

Lines changed: 14 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,6 +41,7 @@ async def poll_api(self):
4141
sunset=result.get_sunset
4242
)
4343
)
44+
4445

4546
def get_temp_color(self, temp: int) -> Tuple[int, int, int]:
4647
if temp >= 100:
@@ -157,12 +158,24 @@ async def render(self, api , loop) -> None:
157158
self.clear()
158159
self.logger.debug("Reloading Image in matrix")
159160
self.reload_image()
161+
xpos = 0
162+
self.logger.info("Loading Screen 2 of Matrix")
163+
while xpos < 100:
164+
self.reload_image()
165+
self.render_location(api, xpos)
166+
self.render_icon(api)
167+
self.render_humidity(api)
168+
self.render_wind(api)
169+
self.render_time(api)
170+
await self.render_image()
171+
xpos += 1
172+
time.sleep(3) if xpos == 1 else time.sleep(.05)
173+
self.reload_image()
160174
self.render_location(api, 0)
161175
self.render_icon(api)
162176
self.render_humidity(api)
163177
self.render_wind(api)
164178
self.render_time(api)
165-
self.logger.info("Loading Screen 2 of Matrix")
166179
await self.render_image()
167180
time.sleep(30)
168181

setup.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,7 +32,8 @@
3232
"aiohttp",
3333
"iso6709",
3434
"sportsipy",
35-
"wget"
35+
"wget",
36+
"suntime"
3637
],
3738
setup_requires=["pytest-runner"],
3839
tests_require=["pytest==4.4.1"],

0 commit comments

Comments
 (0)