Skip to content

Commit

Permalink
New weather plugin, improvements to ping and telemetry
Browse files Browse the repository at this point in the history
  • Loading branch information
berticus2016 committed May 12, 2023
1 parent 670507c commit 48a6f34
Show file tree
Hide file tree
Showing 5 changed files with 152 additions and 5 deletions.
10 changes: 10 additions & 0 deletions db_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,16 @@ def store_plugin_data(plugin_name, meshtastic_id, data):
conn.commit()


def delete_plugin_data(plugin_name, meshtastic_id):
with sqlite3.connect("meshtastic.sqlite") as conn:
cursor = conn.cursor()
cursor.execute(
"DELETE FROM plugin_data WHERE plugin_name=? AND meshtastic_id=?",
(plugin_name, meshtastic_id),
)
conn.commit()


# Get the data for a given plugin and Meshtastic ID
def get_plugin_data_for_node(plugin_name, meshtastic_id):
with sqlite3.connect("meshtastic.sqlite") as conn:
Expand Down
21 changes: 19 additions & 2 deletions plugins/base_plugin.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
from abc import ABC, abstractmethod
from log_utils import get_logger
from config import relay_config
from db_utils import store_plugin_data, get_plugin_data, get_plugin_data_for_node
from db_utils import (
store_plugin_data,
get_plugin_data,
get_plugin_data_for_node,
delete_plugin_data,
)
from matrix_utils import bot_command


Expand All @@ -16,10 +21,22 @@ def __init__(self) -> None:
if "plugins" in relay_config and self.plugin_name in relay_config["plugins"]:
self.config = relay_config["plugins"][self.plugin_name]

def store_node_data(self, meshtastic_id, data):
def store_node_data(self, meshtastic_id, node_data):
data = self.get_node_data(meshtastic_id=meshtastic_id)
data = data[-self.max_data_rows_per_node :]
if type(node_data) is list:
data.extend(node_data)
else:
data.append(node_data)
store_plugin_data(self.plugin_name, meshtastic_id, data)

def set_node_data(self, meshtastic_id, node_data):
node_data = node_data[-self.max_data_rows_per_node :]
store_plugin_data(self.plugin_name, meshtastic_id, node_data)

def delete_node_data(self, meshtastic_id):
return delete_plugin_data(self.plugin_name, meshtastic_id)

def get_node_data(self, meshtastic_id):
return get_plugin_data_for_node(self.plugin_name, meshtastic_id)

Expand Down
19 changes: 17 additions & 2 deletions plugins/ping.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@

from plugins.base_plugin import BasePlugin
from matrix_utils import connect_matrix
from meshtastic_utils import connect_meshtastic


class Plugin(BasePlugin):
Expand All @@ -10,12 +11,25 @@ class Plugin(BasePlugin):
async def handle_meshtastic_message(
self, packet, formatted_message, longname, meshnet_name
):
pass
if (
"decoded" in packet
and "portnum" in packet["decoded"]
and packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP"
and "text" in packet["decoded"]
):
message = packet["decoded"]["text"]
message = message.strip()
if f"!{self.plugin_name}" not in message:
return

meshtastic_client = connect_meshtastic()
meshtastic_client.sendText(text="pong!", destinationId=packet["fromId"])
return True

async def handle_room_message(self, room, event, full_message):
full_message = full_message.strip()
if not self.matches(full_message):
return
return False

matrix_client = await connect_matrix()
response = await matrix_client.room_send(
Expand All @@ -26,3 +40,4 @@ async def handle_room_message(self, room, event, full_message):
"body": "pong!",
},
)
return True
2 changes: 1 addition & 1 deletion plugins/telemetry.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,7 @@ async def handle_meshtastic_message(
"airUtilTx": packet_data["deviceMetrics"]["airUtilTx"],
}
)
self.store_node_data(meshtastic_id=packet["fromId"], data=telemetry_data)
self.set_node_data(meshtastic_id=packet["fromId"], node_data=telemetry_data)
return False

def matches(self, payload):
Expand Down
105 changes: 105 additions & 0 deletions plugins/weather_plugin.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import re
import requests

from plugins.base_plugin import BasePlugin
from matrix_utils import connect_matrix
from meshtastic_utils import connect_meshtastic


class Plugin(BasePlugin):
plugin_name = "weather"

def generate_forecast(self, latitude, longitude):
url = f"https://api.open-meteo.com/v1/forecast?latitude={latitude}&longitude={longitude}&hourly=temperature_2m,precipitation_probability,weathercode,cloudcover&forecast_days=1&current_weather=true"

try:
response = requests.get(url)
data = response.json()

# Extract relevant weather data
current_temp = data["current_weather"]["temperature"]
current_weather_code = data["current_weather"]["weathercode"]
is_day = data["current_weather"]["is_day"]

forecast_2h_temp = data["hourly"]["temperature_2m"][2]
forecast_2h_precipitation = data["hourly"]["precipitation_probability"][2]
forecast_2h_weather_code = data["hourly"]["weathercode"][2]

forecast_5h_temp = data["hourly"]["temperature_2m"][5]
forecast_5h_precipitation = data["hourly"]["precipitation_probability"][5]
forecast_5h_weather_code = data["hourly"]["weathercode"][5]

def weather_code_to_text(weather_code, is_day):
weather_mapping = {
0: "☀️ Sunny" if is_day else "🌙 Clear",
1: "⛅️ Partly Cloudy" if is_day else "🌙⛅️ Clear",
2: "🌤️ Mostly Clear" if is_day else "🌙🌤️ Mostly Clear",
3: "🌥️ Mostly Cloudy" if is_day else "🌙🌥️ Mostly Clear",
4: "☁️ Cloudy" if is_day else "🌙☁️ Cloudy",
5: "🌧️ Rainy" if is_day else "🌙🌧️ Rainy",
6: "⛈️ Thunderstorm" if is_day else "🌙⛈️ Thunderstorm",
7: "❄️ Snowy" if is_day else "🌙❄️ Snowy",
8: "🌧️❄️ Wintry Mix" if is_day else "🌙🌧️❄️ Wintry Mix",
9: "🌫️ Foggy" if is_day else "🌙🌫️ Foggy",
10: "💨 Windy" if is_day else "🌙💨 Windy",
11: "🌧️☈️ Stormy/Hail" if is_day else "🌙🌧️☈️ Stormy/Hail",
12: "🌫️ Foggy" if is_day else "🌙🌫️ Foggy",
13: "🌫️ Foggy" if is_day else "🌙🌫️ Foggy",
14: "🌫️ Foggy" if is_day else "🌙🌫️ Foggy",
15: "🌋 Volcanic Ash" if is_day else "🌙🌋 Volcanic Ash",
16: "🌧️ Rainy" if is_day else "🌙🌧️ Rainy",
17: "🌫️ Foggy" if is_day else "🌙🌫️ Foggy",
18: "🌪️ Tornado" if is_day else "🌙🌪️ Tornado",
}

return weather_mapping.get(weather_code, "❓ Unknown")

# Generate one-line weather forecast
forecast = f"Now: {weather_code_to_text(current_weather_code, is_day)} - {current_temp}°C | "
forecast += f"+2h: {weather_code_to_text(forecast_2h_weather_code, is_day)} - {forecast_2h_temp}°C {forecast_2h_precipitation}% | "
forecast += f"+5h: {weather_code_to_text(forecast_5h_weather_code, is_day)} - {forecast_5h_temp}°C {forecast_5h_precipitation}%"

return forecast

except requests.exceptions.RequestException as e:
print(f"Error: {e}")
return None

async def handle_meshtastic_message(
self, packet, formatted_message, longname, meshnet_name
):
if (
"decoded" in packet
and "portnum" in packet["decoded"]
and packet["decoded"]["portnum"] == "TEXT_MESSAGE_APP"
and "text" in packet["decoded"]
):
message = packet["decoded"]["text"]
message = message.strip()

if f"!{self.plugin_name}" not in message:
return False

meshtastic_client = connect_meshtastic()
if packet["fromId"] in meshtastic_client.nodes:
weather_notice = "Cannot determine location"
requesting_node = meshtastic_client.nodes.get(packet["fromId"])
if (
requesting_node
and "position" in requesting_node
and "latitude" in requesting_node["position"]
and "longitude" in requesting_node["position"]
):
weather_notice = self.generate_forecast(
latitude=requesting_node["position"]["latitude"],
longitude=requesting_node["position"]["longitude"],
)

meshtastic_client.sendText(
text=weather_notice,
destinationId=packet["fromId"],
)
return True

async def handle_room_message(self, room, event, full_message):
return False

0 comments on commit 48a6f34

Please sign in to comment.