kopia lustrzana https://github.com/mate-dev/meshtastic-matrix-relay
365 wiersze
12 KiB
Python
365 wiersze
12 KiB
Python
import os
|
|
import glob
|
|
import yaml
|
|
import webbrowser
|
|
import tkinter as tk
|
|
from tkinter import messagebox
|
|
from tkinter import ttk
|
|
from collections import OrderedDict
|
|
|
|
|
|
def create_default_config():
|
|
default_config = {
|
|
"matrix": {
|
|
"homeserver": "",
|
|
"bot_user_id": "",
|
|
"access_token": ""
|
|
},
|
|
"matrix_rooms": [],
|
|
"logging": {
|
|
"level": "info"
|
|
},
|
|
"plugins": []
|
|
}
|
|
|
|
with open("config.yaml", "w") as f:
|
|
yaml.dump(default_config, f)
|
|
return default_config
|
|
|
|
def load_config():
|
|
try:
|
|
with open("config.yaml", "r") as f:
|
|
return yaml.safe_load(f)
|
|
except FileNotFoundError:
|
|
return create_default_config()
|
|
|
|
def validate_config():
|
|
room_ids = [frame.room_id_var.get() for frame in matrix_rooms_frames]
|
|
meshtastic_channels = [int(frame.meshtastic_channel_var.get()) for frame in matrix_rooms_frames]
|
|
|
|
if len(room_ids) != len(set(room_ids)):
|
|
messagebox.showerror("Error", "Each Matrix room must be unique. Please check the room IDs.")
|
|
return False
|
|
|
|
if len(meshtastic_channels) != len(set(meshtastic_channels)):
|
|
messagebox.showerror("Error", "Each Meshtastic channel must be unique. Please check the channel numbers.")
|
|
return False
|
|
|
|
return True
|
|
|
|
def save_config(config):
|
|
with open("config.yaml", "w") as f:
|
|
ordered_yaml_dump(config, f)
|
|
|
|
|
|
def update_minsize(): # Function that prevents the window from resizing too small
|
|
root.update_idletasks()
|
|
root.minsize(root.winfo_width(), root.winfo_height())
|
|
|
|
|
|
|
|
class Hyperlink(tk.Label):
|
|
def __init__(self, master=None, **kwargs):
|
|
self.default_color = kwargs.pop("fg", "blue")
|
|
self.hover_color = kwargs.pop("hover_color", "darkblue")
|
|
super().__init__(master, fg=self.default_color, cursor="hand2", **kwargs)
|
|
self.bind("<Enter>", self.on_enter)
|
|
self.bind("<Leave>", self.on_leave)
|
|
self.bind("<Button-1>", self.on_click)
|
|
|
|
def on_enter(self, event):
|
|
self.config(fg=self.hover_color)
|
|
|
|
def on_leave(self, event):
|
|
self.config(fg=self.default_color)
|
|
|
|
def on_click(self, event):
|
|
webbrowser.open(self.cget("text"))
|
|
|
|
# Functions
|
|
def ordered_yaml_dump(data, stream=None, Dumper=yaml.Dumper, **kwds):
|
|
class OrderedDumper(Dumper):
|
|
pass
|
|
|
|
def _dict_representer(dumper, data):
|
|
return dumper.represent_mapping(
|
|
yaml.resolver.BaseResolver.DEFAULT_MAPPING_TAG,
|
|
data.items()
|
|
)
|
|
|
|
OrderedDumper.add_representer(OrderedDict, _dict_representer)
|
|
return yaml.dump(data, stream, OrderedDumper, **kwds)
|
|
|
|
|
|
def get_plugin_names():
|
|
plugin_files = glob.glob("./plugins/*_plugin.py")
|
|
plugin_files = [p for p in plugin_files if os.path.basename(p) != "base_plugin.py"]
|
|
plugin_names = [os.path.basename(p)[:-10] for p in plugin_files]
|
|
return plugin_names
|
|
|
|
def create_meshtastic_frame(root):
|
|
frame = tk.LabelFrame(root, text="Meshtastic", padx=5, pady=5)
|
|
frame.pack(fill="x", padx=5, pady=5)
|
|
|
|
connection_types = ["serial", "network"]
|
|
connection_type_var = tk.StringVar(value=config["meshtastic"]["connection_type"])
|
|
|
|
for i, ctype in enumerate(connection_types):
|
|
radio_button = tk.Radiobutton(frame, text=ctype, variable=connection_type_var, value=ctype)
|
|
radio_button.grid(row=0, column=i, padx=5)
|
|
|
|
serial_port_label = tk.Label(frame, text="Serial Port:")
|
|
serial_port_label.grid(row=1, column=0, sticky="w")
|
|
serial_port_var = tk.StringVar(value=config["meshtastic"]["serial_port"])
|
|
serial_port_entry = tk.Entry(frame, textvariable=serial_port_var)
|
|
serial_port_entry.grid(row=1, column=1, sticky="ew")
|
|
|
|
host_label = tk.Label(frame, text="Host:")
|
|
host_label.grid(row=2, column=0, sticky="w")
|
|
host_var = tk.StringVar(value=config["meshtastic"]["host"])
|
|
host_entry = tk.Entry(frame, textvariable=host_var)
|
|
host_entry.grid(row=2, column=1, sticky="ew")
|
|
|
|
meshnet_name_label = tk.Label(frame, text="Meshnet Name:")
|
|
meshnet_name_label.grid(row=3, column=0, sticky="w")
|
|
meshnet_name_var = tk.StringVar(value=config["meshtastic"]["meshnet_name"])
|
|
meshnet_name_entry = tk.Entry(frame, textvariable=meshnet_name_var)
|
|
meshnet_name_entry.grid(row=3, column=1, sticky="ew")
|
|
|
|
broadcast_enabled_label = tk.Label(frame, text="Broadcast Enabled:")
|
|
broadcast_enabled_label.grid(row=4, column=0, sticky="w")
|
|
broadcast_enabled_var = tk.BooleanVar(value=config["meshtastic"]["broadcast_enabled"])
|
|
broadcast_enabled_checkbox = tk.Checkbutton(frame, variable=broadcast_enabled_var)
|
|
broadcast_enabled_checkbox.grid(row=4, column=1, sticky="w")
|
|
|
|
return {
|
|
"connection_type": connection_type_var,
|
|
"serial_port": serial_port_var,
|
|
"host": host_var,
|
|
"meshnet_name": meshnet_name_var,
|
|
"broadcast_enabled": broadcast_enabled_var,
|
|
}
|
|
|
|
def create_logging_frame(root):
|
|
frame = tk.LabelFrame(root, text="Logging", padx=5, pady=5)
|
|
frame.pack(fill="x", padx=5, pady=5)
|
|
|
|
logging_options = ["info", "debug"]
|
|
logging_level_var = tk.StringVar(value=config["logging"]["level"])
|
|
|
|
for i, level in enumerate(logging_options):
|
|
radio_button = tk.Radiobutton(frame, text=level, variable=logging_level_var, value=level)
|
|
radio_button.grid(row=0, column=i, padx=5)
|
|
|
|
return logging_level_var
|
|
|
|
def create_plugins_frame(root):
|
|
frame = tk.LabelFrame(root, text="Plugins", padx=5, pady=5)
|
|
frame.pack(fill="x", padx=5, pady=5)
|
|
|
|
plugin_names = get_plugin_names()
|
|
plugin_vars = {}
|
|
|
|
for i, plugin in enumerate(plugin_names):
|
|
# Create a plugin-specific frame
|
|
plugin_frame = tk.LabelFrame(frame, text=plugin, padx=5, pady=5)
|
|
plugin_frame.grid(row=i, column=0, padx=5, pady=5, sticky="ew")
|
|
|
|
frame.columnconfigure(0, weight=1)
|
|
|
|
active_var = tk.BooleanVar(value=config["plugins"][plugin]["active"])
|
|
checkbox = tk.Checkbutton(plugin_frame, text="Active", variable=active_var)
|
|
checkbox.grid(row=0, column=0)
|
|
|
|
plugin_vars[plugin] = {"active": active_var}
|
|
|
|
nested_keys = [k for k in config["plugins"][plugin] if k != "active"]
|
|
for j, nested_key in enumerate(nested_keys):
|
|
label = tk.Label(plugin_frame, text=nested_key)
|
|
label.grid(row=0, column=2 * j + 1, padx=(10, 0))
|
|
|
|
nested_var_value = config["plugins"][plugin][nested_key]
|
|
|
|
if isinstance(nested_var_value, bool):
|
|
nested_var = tk.BooleanVar(value=nested_var_value)
|
|
entry = tk.Checkbutton(plugin_frame, variable=nested_var)
|
|
else:
|
|
nested_var = tk.StringVar(value=nested_var_value)
|
|
entry = tk.Entry(plugin_frame, textvariable=nested_var, width=len(nested_var_value) + 1) # Change the width here
|
|
entry.bind('<KeyRelease>', lambda event: update_entry_width(event, entry))
|
|
|
|
entry.grid(row=0, column=2 * j + 2)
|
|
|
|
plugin_vars[plugin][nested_key] = nested_var
|
|
|
|
return plugin_vars
|
|
|
|
|
|
|
|
# Add the update_entry_width function
|
|
def update_entry_width(event, entry):
|
|
if isinstance(entry, tk.Entry):
|
|
entry.config(width=len(entry.get()) + 1)
|
|
|
|
|
|
|
|
|
|
def apply_changes():
|
|
|
|
# Check if config is valid
|
|
if not validate_config():
|
|
return
|
|
|
|
# Update matrix config
|
|
for key, var in matrix_vars.items():
|
|
config["matrix"][key] = var.get()
|
|
new_config = OrderedDict()
|
|
new_config["matrix"] = config["matrix"]
|
|
new_config["meshtastic"] = config["meshtastic"]
|
|
|
|
# Update matrix_rooms config
|
|
config["matrix_rooms"] = []
|
|
for room_frame in matrix_rooms_frames:
|
|
room_id = room_frame.room_id_var.get()
|
|
meshtastic_channel = room_frame.meshtastic_channel_var.get()
|
|
config["matrix_rooms"].append({"id": room_id, "meshtastic_channel": int(meshtastic_channel)})
|
|
|
|
# Sort matrix_rooms by meshtastic_channel and add to new_config
|
|
new_config["matrix_rooms"] = sorted(config["matrix_rooms"], key=lambda x: x["meshtastic_channel"])
|
|
|
|
new_config["logging"] = config["logging"]
|
|
new_config["plugins"] = config["plugins"]
|
|
|
|
# Update logging config
|
|
config["logging"]["level"] = logging_level_var.get()
|
|
|
|
# Update plugins config
|
|
for plugin, vars in plugin_vars.items():
|
|
plugin_config = {}
|
|
for k, v in vars.items():
|
|
plugin_config[k] = v.get()
|
|
config["plugins"][plugin] = plugin_config
|
|
|
|
# Update meshtastic config
|
|
for key, var in meshtastic_vars.items():
|
|
if key == "broadcast_enabled":
|
|
config["meshtastic"][key] = var.get()
|
|
else:
|
|
config["meshtastic"][key] = var.get()
|
|
|
|
save_config(new_config)
|
|
|
|
root.destroy()
|
|
|
|
|
|
def add_matrix_room(room=None, meshtastic_channel=None):
|
|
if len(matrix_rooms_frames) >= 8:
|
|
messagebox.showerror("Error", "There is a maximum of 8 Meshtastic channels.")
|
|
return
|
|
room_frame = tk.Frame(matrix_rooms_frame)
|
|
room_frame.grid(row=len(matrix_rooms_frames), column=0, padx=5, pady=5, sticky="ew")
|
|
|
|
room_frame.room_id_var = tk.StringVar(value=room or "")
|
|
room_frame.meshtastic_channel_var = tk.StringVar(value=str(meshtastic_channel) if meshtastic_channel is not None else "")
|
|
|
|
|
|
room_id_label = tk.Label(room_frame, text="ID:")
|
|
room_id_label.grid(row=0, column=0)
|
|
|
|
room_id_entry = tk.Entry(room_frame, textvariable=room_frame.room_id_var, width=40)
|
|
room_id_entry.grid(row=0, column=1, padx=(0, 10))
|
|
|
|
meshtastic_channel_label = tk.Label(room_frame, text="Meshtastic Channel:")
|
|
meshtastic_channel_label.grid(row=0, column=2)
|
|
|
|
meshtastic_channel_entry = tk.Entry(room_frame, textvariable=room_frame.meshtastic_channel_var, width=5)
|
|
meshtastic_channel_entry.grid(row=0, column=3)
|
|
|
|
matrix_rooms_frames.append(room_frame)
|
|
update_minsize()
|
|
|
|
def remove_matrix_room():
|
|
if len(matrix_rooms_frames) <= 1:
|
|
messagebox.showerror("Error", "There must be at least one room & channel.")
|
|
return
|
|
if matrix_rooms_frames:
|
|
frame_to_remove = matrix_rooms_frames.pop()
|
|
frame_to_remove.destroy()
|
|
update_minsize()
|
|
|
|
# GUI
|
|
config = load_config()
|
|
|
|
root = tk.Tk()
|
|
root.title("M<>M Relay - Configuration Editor")
|
|
|
|
# Create the main tab control
|
|
tab_control = ttk.Notebook(root)
|
|
tab_control.pack(expand=True, fill="both")
|
|
|
|
# Create the Settings tab
|
|
settings_tab = ttk.Frame(tab_control)
|
|
tab_control.add(settings_tab, text="Settings")
|
|
|
|
# Create the Plugins tab
|
|
plugins_tab = ttk.Frame(tab_control)
|
|
tab_control.add(plugins_tab, text="Plugins")
|
|
|
|
|
|
# Matrix frame
|
|
matrix_frame = tk.LabelFrame(settings_tab, text="Matrix", padx=5, pady=5)
|
|
matrix_frame.pack(padx=10, pady=10, fill="x", expand="yes")
|
|
|
|
matrix_keys = ["homeserver", "bot_user_id", "access_token"]
|
|
matrix_vars = {}
|
|
|
|
for i, key in enumerate(matrix_keys):
|
|
label = tk.Label(matrix_frame, text=key)
|
|
label.grid(row=i, column=0, sticky="w")
|
|
|
|
var = tk.StringVar(value=config["matrix"][key])
|
|
entry = tk.Entry(matrix_frame, textvariable=var, width=49)
|
|
entry.grid(row=i, column=1, sticky="ew")
|
|
matrix_vars[key] = var
|
|
|
|
# Add instruction label
|
|
instruction_label = tk.Label(matrix_frame, text="For instructions on where to find your access token, visit:")
|
|
instruction_label.grid(row=3, column=0, columnspan=2, sticky="ew")
|
|
|
|
# Add hyperlink label
|
|
link_label = Hyperlink(matrix_frame, text="https://t2bot.io/docs/access_tokens/")
|
|
link_label.grid(row=4, column=0, columnspan=2, sticky="ew")
|
|
|
|
# Create meshtastic frame
|
|
meshtastic_vars = create_meshtastic_frame(settings_tab)
|
|
|
|
# Matrix rooms frame
|
|
matrix_rooms_frame = tk.LabelFrame(settings_tab, text="Matrix Rooms", padx=5, pady=5)
|
|
matrix_rooms_frame.pack(padx=10, pady=10, fill="both", expand="yes")
|
|
|
|
matrix_rooms_frames = []
|
|
|
|
for room in config["matrix_rooms"]:
|
|
add_matrix_room(room["id"], room["meshtastic_channel"])
|
|
|
|
add_remove_frame = tk.Frame(matrix_rooms_frame)
|
|
add_remove_frame.grid(row=1000, column=0, padx=5, pady=5, sticky="ew")
|
|
|
|
add_button = tk.Button(add_remove_frame, text="+", command=add_matrix_room)
|
|
add_button.pack(side="left")
|
|
|
|
remove_button = tk.Button(add_remove_frame, text="-", command=remove_matrix_room)
|
|
remove_button.pack(side="left")
|
|
|
|
# Create logging frame
|
|
logging_level_var = create_logging_frame(settings_tab)
|
|
|
|
# Create plugins frame
|
|
plugin_vars = create_plugins_frame(plugins_tab)
|
|
|
|
# Apply button
|
|
apply_button = tk.Button(root, text="Save & Launch Relay", command=apply_changes)
|
|
apply_button.pack(side="bottom", pady=10)
|
|
|
|
root.mainloop()
|