Improvements to mapping, and retry capability. Plugin scheduling is now supported

pull/1/head
Geoff Whittington 2023-06-02 22:32:10 -04:00
rodzic a6a6c2c81f
commit 0f3fef3269
4 zmienionych plików z 228 dodań i 4 usunięć

Wyświetl plik

@ -1,4 +1,5 @@
import asyncio
import time
import meshtastic.tcp_interface
import meshtastic.serial_interface
from typing import List
@ -22,14 +23,56 @@ def connect_meshtastic():
return meshtastic_client
# Initialize Meshtastic interface
connection_type = relay_config["meshtastic"]["connection_type"]
retry_limit = (
relay_config["meshtastic"]["retry_limit"]
if "retry_limit" in relay_config["meshtastic"]
else 3
)
attempts = 1
successful = False
if connection_type == "serial":
serial_port = relay_config["meshtastic"]["serial_port"]
logger.info(f"Connecting to serial port {serial_port} ...")
meshtastic_client = meshtastic.serial_interface.SerialInterface(serial_port)
while not successful and attempts <= retry_limit:
try:
meshtastic_client = meshtastic.serial_interface.SerialInterface(
serial_port
)
successful = True
except Exception as e:
attempts += 1
if attempts <= retry_limit:
logger.warn(
f"Attempt #{attempts-1} failed. Retrying in {attempts} secs {e}"
)
time.sleep(attempts)
else:
logger.error(f"Could not connect: {e}")
return None
else:
target_host = relay_config["meshtastic"]["host"]
logger.info(f"Connecting to host {target_host} ...")
meshtastic_client = meshtastic.tcp_interface.TCPInterface(hostname=target_host)
while not successful and attempts <= retry_limit:
try:
meshtastic_client = meshtastic.tcp_interface.TCPInterface(
hostname=target_host
)
successful = True
except Exception as e:
attempts += 1
if attempts <= retry_limit:
logger.warn(
f"Attempt #{attempts-1} failed. Retrying in {attempts} secs... {e}"
)
time.sleep(attempts)
else:
logger.error(f"Could not connect: {e}")
return None
nodeInfo = meshtastic_client.getMyNodeInfo()
logger.info(
f"Connected to {nodeInfo['user']['shortName']} / {nodeInfo['user']['hwModel']}"
)
return meshtastic_client

Wyświetl plik

@ -42,8 +42,8 @@ def load_plugins():
if "priority" in plugin.config
else plugin.priority
)
logger.info(f"Loaded {plugin.plugin_name} ({plugin.priority})")
active_plugins.append(plugin)
plugin.start()
sorted_active_plugins = sorted(active_plugins, key=lambda plugin: plugin.priority)
return sorted_active_plugins

Wyświetl plik

@ -1,4 +1,7 @@
import markdown
import schedule
import threading
import time
from abc import ABC, abstractmethod
from log_utils import get_logger
from config import relay_config
@ -26,6 +29,49 @@ class BasePlugin(ABC):
if "plugins" in relay_config and self.plugin_name in relay_config["plugins"]:
self.config = relay_config["plugins"][self.plugin_name]
def start(self):
if "schedule" not in self.config or (
"at" not in self.config["schedule"]
and "hours" not in self.config["schedule"]
and "minutes" not in self.config["schedule"]
):
self.logger.debug(f"Started with priority={self.priority}")
return
# Schedule the email-checking function to run every minute
if "at" in self.config["schedule"] and "hours" in self.config["schedule"]:
schedule.every(self.config["schedule"]["hours"]).hours.at(
self.config["schedule"]["at"]
).do(self.background_job)
elif "at" in self.config["schedule"] and "minutes" in self.config["schedule"]:
schedule.every(self.config["schedule"]["minutes"]).minutes.at(
self.config["schedule"]["at"]
).do(self.background_job)
elif "hours" in self.config["schedule"]:
schedule.every(self.config["schedule"]["hours"]).hours.do(
self.background_job
)
elif "minutes" in self.config["schedule"]:
schedule.every(self.config["schedule"]["minutes"]).minutes.do(
self.background_job
)
# Function to execute the scheduled tasks
def run_schedule():
while True:
schedule.run_pending()
time.sleep(1)
# Create a thread for executing the scheduled tasks
schedule_thread = threading.Thread(target=run_schedule)
# Start the thread
schedule_thread.start()
self.logger.debug(f"Scheduled with priority={self.priority}")
def background_job(self):
pass
def strip_raw(self, data):
if type(data) is not dict:
return data

Wyświetl plik

@ -1,4 +1,5 @@
import staticmaps
import s2sphere
import math
import random
import io
@ -8,6 +9,139 @@ from nio import AsyncClient, UploadResponse
from plugins.base_plugin import BasePlugin
class TextLabel(staticmaps.Object):
def __init__(self, latlng: s2sphere.LatLng, text: str, fontSize: int = 12) -> None:
staticmaps.Object.__init__(self)
self._latlng = latlng
self._text = text
self._margin = 4
self._arrow = 16
self._font_size = fontSize
print(self._font_size)
def latlng(self) -> s2sphere.LatLng:
return self._latlng
def bounds(self) -> s2sphere.LatLngRect:
return s2sphere.LatLngRect.from_point(self._latlng)
def extra_pixel_bounds(self) -> staticmaps.PixelBoundsT:
# Guess text extents.
tw = len(self._text) * self._font_size * 0.5
th = self._font_size * 1.2
w = max(self._arrow, tw + 2.0 * self._margin)
return (int(w / 2.0), int(th + 2.0 * self._margin + self._arrow), int(w / 2), 0)
def render_pillow(self, renderer: staticmaps.PillowRenderer) -> None:
x, y = renderer.transformer().ll2pixel(self.latlng())
x = x + renderer.offset_x()
tw, th = renderer.draw().textsize(self._text)
w = max(self._arrow, tw + 2 * self._margin)
h = th + 2 * self._margin
path = [
(x, y),
(x + self._arrow / 2, y - self._arrow),
(x + w / 2, y - self._arrow),
(x + w / 2, y - self._arrow - h),
(x - w / 2, y - self._arrow - h),
(x - w / 2, y - self._arrow),
(x - self._arrow / 2, y - self._arrow),
]
renderer.draw().polygon(path, fill=(255, 255, 255, 255))
renderer.draw().line(path, fill=(255, 0, 0, 255))
renderer.draw().text(
(x - tw / 2, y - self._arrow - h / 2 - th / 2),
self._text,
fill=(0, 0, 0, 255),
)
def render_cairo(self, renderer: staticmaps.CairoRenderer) -> None:
x, y = renderer.transformer().ll2pixel(self.latlng())
ctx = renderer.context()
ctx.select_font_face("Sans", cairo.FONT_SLANT_NORMAL, cairo.FONT_WEIGHT_NORMAL)
ctx.set_font_size(self._font_size)
x_bearing, y_bearing, tw, th, _, _ = ctx.text_extents(self._text)
w = max(self._arrow, tw + 2 * self._margin)
h = th + 2 * self._margin
path = [
(x, y),
(x + self._arrow / 2, y - self._arrow),
(x + w / 2, y - self._arrow),
(x + w / 2, y - self._arrow - h),
(x - w / 2, y - self._arrow - h),
(x - w / 2, y - self._arrow),
(x - self._arrow / 2, y - self._arrow),
]
ctx.set_source_rgb(1, 1, 1)
ctx.new_path()
for p in path:
ctx.line_to(*p)
ctx.close_path()
ctx.fill()
ctx.set_source_rgb(1, 0, 0)
ctx.set_line_width(1)
ctx.new_path()
for p in path:
ctx.line_to(*p)
ctx.close_path()
ctx.stroke()
ctx.set_source_rgb(0, 0, 0)
ctx.set_line_width(1)
ctx.move_to(
x - tw / 2 - x_bearing, y - self._arrow - h / 2 - y_bearing - th / 2
)
ctx.show_text(self._text)
ctx.stroke()
def render_svg(self, renderer: staticmaps.SvgRenderer) -> None:
x, y = renderer.transformer().ll2pixel(self.latlng())
# guess text extents
tw = len(self._text) * self._font_size * 0.5
th = self._font_size * 1.2
w = max(self._arrow, tw + 2 * self._margin)
h = th + 2 * self._margin
path = renderer.drawing().path(
fill="#ffffff",
stroke="#ff0000",
stroke_width=1,
opacity=1.0,
)
path.push(f"M {x} {y}")
path.push(f" l {self._arrow / 2} {-self._arrow}")
path.push(f" l {w / 2 - self._arrow / 2} 0")
path.push(f" l 0 {-h}")
path.push(f" l {-w} 0")
path.push(f" l 0 {h}")
path.push(f" l {w / 2 - self._arrow / 2} 0")
path.push("Z")
renderer.group().add(path)
renderer.group().add(
renderer.drawing().text(
self._text,
text_anchor="middle",
dominant_baseline="central",
insert=(x, y - self._arrow - h / 2),
font_family="sans-serif",
font_size=f"{self._font_size}px",
fill="#000000",
)
)
def anonymize_location(lat, lon, radius=1000):
# Generate random offsets for latitude and longitude
lat_offset = random.uniform(-radius / 111320, radius / 111320)
@ -42,7 +176,7 @@ def get_map(locations, zoom=None, image_size=None, anonymize=True, radius=10000)
radio = staticmaps.create_latlng(
float(location["lat"]), float(location["lon"])
)
context.add_object(staticmaps.Marker(radio, size=10))
context.add_object(TextLabel(radio, location["label"], fontSize=50))
# render non-anti-aliased png
if image_size:
@ -148,6 +282,7 @@ class Plugin(BasePlugin):
{
"lat": info["position"]["latitude"],
"lon": info["position"]["longitude"],
"label": info["user"]["shortName"],
}
)