kopia lustrzana https://github.com/mate-dev/meshtastic-matrix-relay
New weather plugin, improvements to ping and telemetry
rodzic
a82d9dbe82
commit
e0f47a8da6
10
db_utils.py
10
db_utils.py
|
@ -25,6 +25,16 @@ def store_plugin_data(plugin_name, meshtastic_id, data):
|
||||||
conn.commit()
|
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
|
# Get the data for a given plugin and Meshtastic ID
|
||||||
def get_plugin_data_for_node(plugin_name, meshtastic_id):
|
def get_plugin_data_for_node(plugin_name, meshtastic_id):
|
||||||
with sqlite3.connect("meshtastic.sqlite") as conn:
|
with sqlite3.connect("meshtastic.sqlite") as conn:
|
||||||
|
|
|
@ -1,7 +1,12 @@
|
||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from log_utils import get_logger
|
from log_utils import get_logger
|
||||||
from config import relay_config
|
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
|
from matrix_utils import bot_command
|
||||||
|
|
||||||
|
|
||||||
|
@ -16,10 +21,22 @@ class BasePlugin(ABC):
|
||||||
if "plugins" in relay_config and self.plugin_name in relay_config["plugins"]:
|
if "plugins" in relay_config and self.plugin_name in relay_config["plugins"]:
|
||||||
self.config = relay_config["plugins"][self.plugin_name]
|
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 :]
|
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)
|
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):
|
def get_node_data(self, meshtastic_id):
|
||||||
return get_plugin_data_for_node(self.plugin_name, meshtastic_id)
|
return get_plugin_data_for_node(self.plugin_name, meshtastic_id)
|
||||||
|
|
||||||
|
|
|
@ -2,6 +2,7 @@ import re
|
||||||
|
|
||||||
from plugins.base_plugin import BasePlugin
|
from plugins.base_plugin import BasePlugin
|
||||||
from matrix_utils import connect_matrix
|
from matrix_utils import connect_matrix
|
||||||
|
from meshtastic_utils import connect_meshtastic
|
||||||
|
|
||||||
|
|
||||||
class Plugin(BasePlugin):
|
class Plugin(BasePlugin):
|
||||||
|
@ -10,12 +11,25 @@ class Plugin(BasePlugin):
|
||||||
async def handle_meshtastic_message(
|
async def handle_meshtastic_message(
|
||||||
self, packet, formatted_message, longname, meshnet_name
|
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):
|
async def handle_room_message(self, room, event, full_message):
|
||||||
full_message = full_message.strip()
|
full_message = full_message.strip()
|
||||||
if not self.matches(full_message):
|
if not self.matches(full_message):
|
||||||
return
|
return False
|
||||||
|
|
||||||
matrix_client = await connect_matrix()
|
matrix_client = await connect_matrix()
|
||||||
response = await matrix_client.room_send(
|
response = await matrix_client.room_send(
|
||||||
|
@ -26,3 +40,4 @@ class Plugin(BasePlugin):
|
||||||
"body": "pong!",
|
"body": "pong!",
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
|
return True
|
||||||
|
|
|
@ -51,7 +51,7 @@ class Plugin(BasePlugin):
|
||||||
"airUtilTx": packet_data["deviceMetrics"]["airUtilTx"],
|
"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
|
return False
|
||||||
|
|
||||||
def matches(self, payload):
|
def matches(self, payload):
|
||||||
|
|
|
@ -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¤t_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
|
Ładowanie…
Reference in New Issue