mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-08-07 09:22:44 +02:00
Vendored
+11
@@ -0,0 +1,11 @@
|
||||
{
|
||||
"python.testing.unittestArgs": [
|
||||
"-v",
|
||||
"-s",
|
||||
".",
|
||||
"-p",
|
||||
"*test.py"
|
||||
],
|
||||
"python.testing.pytestEnabled": false,
|
||||
"python.testing.unittestEnabled": true
|
||||
}
|
||||
+109
-93
@@ -1,6 +1,7 @@
|
||||
# helper functions to use location data
|
||||
# K7MHI Kelly Keeton 2024
|
||||
|
||||
import json
|
||||
from geopy.geocoders import Nominatim # pip install geopy
|
||||
import maidenhead as mh # pip install maidenhead
|
||||
import requests # pip install requests
|
||||
@@ -8,11 +9,14 @@ import bs4 as bs # pip install beautifulsoup4
|
||||
|
||||
URL_TIMEOUT = 10 # wait time for URL requests
|
||||
DAYS_OF_WEATHER = 4 # weather forecast days, the first two rows are today and tonight
|
||||
# unified error messages to be able to test them from tests
|
||||
NO_DATA_NOGPS = "no location data: does your device have GPS?"
|
||||
ERROR_FETCHING_DATA = "error fetching data"
|
||||
|
||||
def where_am_i(lat=0, lon=0):
|
||||
whereIam = ""
|
||||
if float(lat) == 0 and float(lon) == 0:
|
||||
return "no location data: does your device have GPS?"
|
||||
return NO_DATA_NOGPS
|
||||
# initialize Nominatim API
|
||||
geolocator = Nominatim(user_agent="mesh-bot")
|
||||
|
||||
@@ -28,107 +32,119 @@ def where_am_i(lat=0, lon=0):
|
||||
def get_tide(lat=0, lon=0):
|
||||
station_id = ""
|
||||
if float(lat) == 0 and float(lon) == 0:
|
||||
return "no location data: does your device have GPS?"
|
||||
return NO_DATA_NOGPS
|
||||
station_lookup_url = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/tidepredstations.json?lat=" + str(lat) + "&lon=" + str(lon) + "&radius=50"
|
||||
station_data = requests.get(station_lookup_url, timeout=URL_TIMEOUT)
|
||||
if(station_data.ok):
|
||||
station_json = station_data.json()
|
||||
# get first station id in 50 mile radius
|
||||
station_id = station_json['stationList'][0]['stationId']
|
||||
else:
|
||||
return "error fetching station data"
|
||||
try:
|
||||
station_data = requests.get(station_lookup_url, timeout=URL_TIMEOUT)
|
||||
if station_data.ok:
|
||||
station_json = station_data.json()
|
||||
else:
|
||||
return ERROR_FETCHING_DATA
|
||||
except (requests.exceptions.RequestException, json.JSONDecodeError):
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
station_id = station_json['stationList'][0]['stationId']
|
||||
|
||||
station_url = "https://tidesandcurrents.noaa.gov/noaatidepredictions.html?id=" + station_id
|
||||
station_data = requests.get(station_url, timeout=URL_TIMEOUT)
|
||||
if(station_data.ok):
|
||||
# extract table class="table table-condensed"
|
||||
soup = bs.BeautifulSoup(station_data.text, 'html.parser')
|
||||
table = soup.find('table', class_='table table-condensed')
|
||||
|
||||
# extract rows
|
||||
rows = table.find_all('tr')
|
||||
# extract data from rows
|
||||
tide_data = []
|
||||
for row in rows:
|
||||
row_text = ""
|
||||
cols = row.find_all('td')
|
||||
for col in cols:
|
||||
row_text += col.text + " "
|
||||
tide_data.append(row_text)
|
||||
# format tide data into a string
|
||||
tide_string = ""
|
||||
for data in tide_data:
|
||||
tide_string += data + "\n"
|
||||
# trim off last newline
|
||||
tide_string = tide_string[:-1]
|
||||
return tide_string
|
||||
|
||||
else:
|
||||
return "error fetching tide data"
|
||||
try:
|
||||
station_data = requests.get(station_url, timeout=URL_TIMEOUT)
|
||||
if not station_data.ok:
|
||||
return ERROR_FETCHING_DATA
|
||||
except (requests.exceptions.RequestException):
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
# extract table class="table table-condensed"
|
||||
soup = bs.BeautifulSoup(station_data.text, 'html.parser')
|
||||
table = soup.find('table', class_='table table-condensed')
|
||||
|
||||
# extract rows
|
||||
rows = table.find_all('tr')
|
||||
# extract data from rows
|
||||
tide_data = []
|
||||
for row in rows:
|
||||
row_text = ""
|
||||
cols = row.find_all('td')
|
||||
for col in cols:
|
||||
row_text += col.text + " "
|
||||
tide_data.append(row_text)
|
||||
# format tide data into a string
|
||||
tide_string = ""
|
||||
for data in tide_data:
|
||||
tide_string += data + "\n"
|
||||
# trim off last newline
|
||||
tide_string = tide_string[:-1]
|
||||
return tide_string
|
||||
|
||||
def get_weather(lat=0, lon=0, unit=0):
|
||||
weather = ""
|
||||
if float(lat) == 0 and float(lon) == 0:
|
||||
return "no location data: does your device have GPS?"
|
||||
return NO_DATA_NOGPS
|
||||
weather_url = "https://forecast.weather.gov/MapClick.php?FcstType=text&lat=" + str(lat) + "&lon=" + str(lon) + "&unit=" + str(unit)
|
||||
try:
|
||||
weather_data = requests.get(weather_url, timeout=URL_TIMEOUT)
|
||||
if not weather_data.ok:
|
||||
return ERROR_FETCHING_DATA
|
||||
except (requests.exceptions.RequestException):
|
||||
return ERROR_FETCHING_DATA
|
||||
|
||||
if unit == 0:
|
||||
# default to imperial units
|
||||
weather_url = "https://forecast.weather.gov/MapClick.php?FcstType=text&lat=" + str(lat) + "&lon=" + str(lon)
|
||||
|
||||
soup = bs.BeautifulSoup(weather_data.text, 'html.parser')
|
||||
table = soup.find('div', id="detailed-forecast-body")
|
||||
|
||||
if table is None:
|
||||
return "no weather data found on NOAA for your location"
|
||||
else:
|
||||
# metric units
|
||||
weather_url = "https://forecast.weather.gov/MapClick.php?FcstType=text&unit=1&lat=" + str(lat) + "&lon=" + str(lon)
|
||||
|
||||
weather_data = requests.get(weather_url, timeout=URL_TIMEOUT)
|
||||
if(weather_data.ok):
|
||||
soup = bs.BeautifulSoup(weather_data.text, 'html.parser')
|
||||
table = soup.find('div', id="detailed-forecast-body")
|
||||
|
||||
if table is None:
|
||||
return "no weather data found on NOAA for your location"
|
||||
else:
|
||||
# get rows
|
||||
rows = table.find_all('div', class_="row")
|
||||
|
||||
# extract data from rows
|
||||
for row in rows:
|
||||
# shrink the text
|
||||
line = row.text.replace("Monday", "Mon ") \
|
||||
.replace("Tuesday", "Tue ") \
|
||||
.replace("Wednesday", "Wed ") \
|
||||
.replace("Thursday", "Thu ") \
|
||||
.replace("Friday", "Fri ") \
|
||||
.replace("Saturday", "Sat ") \
|
||||
.replace("Today", "Today ") \
|
||||
.replace("Tonight", "Tonight ") \
|
||||
.replace("Tomorrow", "Tomorrow ") \
|
||||
.replace("This Afternoon", "Afternoon ") \
|
||||
.replace("northwest", "NW") \
|
||||
.replace("northeast", "NE") \
|
||||
.replace("southwest", "SW") \
|
||||
.replace("southeast", "SE") \
|
||||
.replace("north", "N") \
|
||||
.replace("south", "S") \
|
||||
.replace("east", "E") \
|
||||
.replace("west", "W") \
|
||||
.replace("Northwest", "NW") \
|
||||
.replace("Northeast", "NE") \
|
||||
.replace("Southwest", "SW") \
|
||||
.replace("Southeast", "SE") \
|
||||
.replace("North", "N") \
|
||||
.replace("South", "S") \
|
||||
.replace("East", "E") \
|
||||
.replace("West", "W") \
|
||||
.replace("precipitation", "precip") \
|
||||
.replace("showers", "shwrs") \
|
||||
.replace("thunderstorms", "t-storms")
|
||||
# only grab a few days of weather
|
||||
if len(weather.split("\n")) < DAYS_OF_WEATHER:
|
||||
weather += line + "\n"
|
||||
# trim off last newline
|
||||
weather = weather[:-1]
|
||||
# get rows
|
||||
rows = table.find_all('div', class_="row")
|
||||
|
||||
return weather
|
||||
# extract data from rows
|
||||
for row in rows:
|
||||
# shrink the text
|
||||
line = replace_weather(row.text)
|
||||
# only grab a few days of weather
|
||||
if len(weather.split("\n")) < DAYS_OF_WEATHER:
|
||||
weather += line + "\n"
|
||||
# trim off last newline
|
||||
weather = weather[:-1]
|
||||
|
||||
else:
|
||||
return "error fetching weather data"
|
||||
|
||||
return weather
|
||||
|
||||
def replace_weather(row):
|
||||
replacements = {
|
||||
"Monday": "Mon ",
|
||||
"Tuesday": "Tue ",
|
||||
"Wednesday": "Wed ",
|
||||
"Thursday": "Thu ",
|
||||
"Friday": "Fri ",
|
||||
"Saturday": "Sat ",
|
||||
"Today": "Today ",
|
||||
"Tonight": "Tonight ",
|
||||
"Tomorrow": "Tomorrow ",
|
||||
"This Afternoon": "Afternoon ",
|
||||
"northwest": "NW",
|
||||
"northeast": "NE",
|
||||
"southwest": "SW",
|
||||
"southeast": "SE",
|
||||
"north": "N",
|
||||
"south": "S",
|
||||
"east": "E",
|
||||
"west": "W",
|
||||
"Northwest": "NW",
|
||||
"Northeast": "NE",
|
||||
"Southwest": "SW",
|
||||
"Southeast": "SE",
|
||||
"North": "N",
|
||||
"South": "S",
|
||||
"East": "E",
|
||||
"West": "W",
|
||||
"precipitation": "precip",
|
||||
"showers": "shwrs",
|
||||
"thunderstorms": "t-storms"
|
||||
}
|
||||
|
||||
line = row
|
||||
for key, value in replacements.items():
|
||||
line = line.replace(key, value)
|
||||
|
||||
return line
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import unittest
|
||||
from locationdata import *
|
||||
|
||||
class TestGetWeather(unittest.TestCase):
|
||||
|
||||
def test_get_weather_with_valid_coordinates(self):
|
||||
# Test with valid coordinates
|
||||
lat = "37.7749"
|
||||
lon = "-122.4194"
|
||||
weather = get_weather(lat, lon)
|
||||
print(f"weather: {weather}")
|
||||
self.assertNotEqual(weather, NO_DATA_NOGPS)
|
||||
self.assertNotEqual(weather, ERROR_FETCHING_DATA)
|
||||
# test replacement works
|
||||
self.assertNotIn(weather, "Sunday")
|
||||
self.assertNotIn(weather, "Monday")
|
||||
self.assertNotIn(weather, "Tuesday")
|
||||
self.assertNotIn(weather, "Wednesday")
|
||||
self.assertNotIn(weather, "Thursday")
|
||||
self.assertNotIn(weather, "Friday")
|
||||
self.assertNotIn(weather, "Saturday")
|
||||
self.assertNotIn(weather, "northwest")
|
||||
self.assertNotIn(weather, "northeast")
|
||||
self.assertNotIn(weather, "southwest")
|
||||
self.assertNotIn(weather, "southeast")
|
||||
self.assertNotIn(weather, "north")
|
||||
self.assertNotIn(weather, "south")
|
||||
self.assertNotIn(weather, "east")
|
||||
self.assertNotIn(weather, "west")
|
||||
self.assertNotIn(weather, "precipitation")
|
||||
self.assertNotIn(weather, "showers")
|
||||
self.assertNotIn(weather, "thunderstorms")
|
||||
|
||||
def test_get_weather_with_invalid_coordinates(self):
|
||||
# Test with invalid coordinates
|
||||
lat = 0
|
||||
lon = 0
|
||||
weather = get_weather(lat, lon)
|
||||
print(f"weather: {weather}")
|
||||
self.assertEqual(weather, NO_DATA_NOGPS)
|
||||
|
||||
def test_where_am_i_with_valid_coordinates(self):
|
||||
# Test with invalid coordinates
|
||||
lat = "37.7749"
|
||||
lon = "-122.4194"
|
||||
location = where_am_i(lat, lon)
|
||||
print(f"location: {location}")
|
||||
self.assertEqual(location, "South Van Ness Avenue San Francisco California 94103 United States Grid: CM87ss")
|
||||
self.assertNotEqual(location, NO_DATA_NOGPS)
|
||||
self.assertNotEqual(location, ERROR_FETCHING_DATA)
|
||||
|
||||
def test_get_tide_with_valid_coordinates(self):
|
||||
# Test with valid coordinates
|
||||
lat = "37.7749"
|
||||
lon = "-122.4194"
|
||||
tide = get_tide(lat, lon)
|
||||
print(f"tide: {tide}")
|
||||
self.assertNotEqual(tide, NO_DATA_NOGPS)
|
||||
self.assertNotEqual(tide, ERROR_FETCHING_DATA)
|
||||
|
||||
def test_replace_weather(self):
|
||||
# Test replacing days of the week
|
||||
self.assertEqual(replace_weather("Monday"), "Mon ")
|
||||
self.assertEqual(replace_weather("Tuesday"), "Tue ")
|
||||
self.assertEqual(replace_weather("Wednesday"), "Wed ")
|
||||
self.assertEqual(replace_weather("Thursday"), "Thu ")
|
||||
self.assertEqual(replace_weather("Friday"), "Fri ")
|
||||
self.assertEqual(replace_weather("Saturday"), "Sat ")
|
||||
|
||||
# Test replacing time periods
|
||||
self.assertEqual(replace_weather("Today"), "Today ")
|
||||
self.assertEqual(replace_weather("Tonight"), "Tonight ")
|
||||
self.assertEqual(replace_weather("Tomorrow"), "Tomorrow ")
|
||||
self.assertEqual(replace_weather("This Afternoon"), "Afternoon ")
|
||||
|
||||
# Test replacing directions
|
||||
self.assertEqual(replace_weather("northwest"), "NW")
|
||||
self.assertEqual(replace_weather("northeast"), "NE")
|
||||
self.assertEqual(replace_weather("southwest"), "SW")
|
||||
self.assertEqual(replace_weather("southeast"), "SE")
|
||||
self.assertEqual(replace_weather("north"), "N")
|
||||
self.assertEqual(replace_weather("south"), "S")
|
||||
self.assertEqual(replace_weather("east"), "E")
|
||||
self.assertEqual(replace_weather("west"), "W")
|
||||
self.assertEqual(replace_weather("Northwest"), "NW")
|
||||
self.assertEqual(replace_weather("Northeast"), "NE")
|
||||
self.assertEqual(replace_weather("Southwest"), "SW")
|
||||
self.assertEqual(replace_weather("Southeast"), "SE")
|
||||
self.assertEqual(replace_weather("North"), "N")
|
||||
self.assertEqual(replace_weather("South"), "S")
|
||||
self.assertEqual(replace_weather("East"), "E")
|
||||
self.assertEqual(replace_weather("West"), "W")
|
||||
|
||||
# Test replacing weather terms
|
||||
self.assertEqual(replace_weather("precipitation"), "precip")
|
||||
self.assertEqual(replace_weather("showers"), "shwrs")
|
||||
self.assertEqual(replace_weather("thunderstorms"), "t-storms")
|
||||
|
||||
|
||||
if __name__ == '__main__':
|
||||
unittest.main()
|
||||
Executable → Regular
+6
-2
@@ -1,16 +1,20 @@
|
||||
# /etc/systemd/system/meshbot.service
|
||||
# sudo systemctl daemon-reload
|
||||
# sudo systemctl start meshbot.service
|
||||
|
||||
[Unit]
|
||||
Description=MESH-BOT
|
||||
After=network.target
|
||||
|
||||
[Service]
|
||||
ExecStart=/usr/bin/python /usr/local/meshing-around/mesh-bot.py
|
||||
ExecStart=/usr/bin/python /usr/local/meshing-around/mesh_bot.py
|
||||
|
||||
# Disable Python's buffering of STDOUT and STDERR, so that output from the
|
||||
# service shows up immediately in systemd's logs
|
||||
Environment=PYTHONUNBUFFERED=1
|
||||
|
||||
Restart=on-failure
|
||||
Type=notify
|
||||
Type=notify #try simple if any problems
|
||||
|
||||
[Install]
|
||||
WantedBy=default.target
|
||||
|
||||
Executable → Regular
@@ -7,3 +7,4 @@ geopy
|
||||
maidenhead
|
||||
beautifulsoup4
|
||||
dadjokes
|
||||
mock
|
||||
Reference in New Issue
Block a user