Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

version 2 #1

Merged
merged 5 commits into from
Jul 1, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,4 @@ cython_debug/
# and can be added to the global gitignore or merged into this file. For a more nuclear
# option (not recommended) you can uncomment the following to ignore the entire idea folder.
#.idea/
/*.db
15 changes: 15 additions & 0 deletions .vscode/launch.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
// Use IntelliSense to learn about possible attributes.
// Hover to view descriptions of existing attributes.
// For more information, visit: https://go.microsoft.com/fwlink/?linkid=830387
"version": "0.2.0",
"configurations": [
{
"name": "Python Debugger: Current File",
"type": "debugpy",
"request": "launch",
"program": "${file}",
"console": "integratedTerminal"
}
]
}
4 changes: 4 additions & 0 deletions .vscode/settings.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,4 +8,8 @@
"python.testing.pytestEnabled": true,
"python.testing.cwd": "${workspaceFolder}/tests",
"python.testing.autoTestDiscoverOnSaveEnabled": true,
"flake8.args": [
"--max-line-length=120 --ignore E402,W503"
],
"editor.defaultFormatter": "ms-python.black-formatter"
}
92 changes: 70 additions & 22 deletions basic_bot.py
Original file line number Diff line number Diff line change
@@ -1,20 +1,22 @@
from meshtastic.stream_interface import StreamInterface
from message_processor import MessageProcessor
from geopy.geocoders import Nominatim
import maidenhead as mh
from dadjokes import Dadjoke


class BasicBot(MessageProcessor):
def __init__(self):
super().__init__()
self.trap_list = ["ping", "ack", "testing", "pong", "lheard", "sitrep", "joke"]
def __init__(self, interface: StreamInterface):
super(BasicBot, self).__init__(interface)
self.trap_list = ["ping", "ack", "lheard", "sitrep", "joke", "whereami"]
pass

def auto_response(self, message, snr, rssi, hop, message_from_id, location:list[float], node_list:list[str]):
print(f"BasicBot: Got message: {message}")

def auto_response(
self, message, snr, rssi, hop, message_from_id, location: list[float]
):
bot_response = None
message = message.lower().strip()
if "ping" in message:
#Check if the user added @foo to the message
bot_response = "PONG, " + f"SNR:{snr} RSSI:{rssi} HOP {hop}"
if " " in message:
bot_response += " and copy: " + message.split(" ")[1]
Expand All @@ -26,37 +28,83 @@ def auto_response(self, message, snr, rssi, hop, message_from_id, location:list[

elif "lheard" in message or "sitrep" in message:
# make a nice list for the user
short_node_list = []
for x in node_list[:5]:
short_node_list.append(f"{x[0]} SNR:{x[2]}")
if not self.node_list:
return "Error Processing Node List"

short_node_list = self.get_node_list()

bot_response = "Last 5 nodes heard:\n" + str("\n".join(short_node_list))
node_list = []
for x in short_node_list:
node_list.append(f"{x[0]} [SNR:{x[2]}]")

bot_response = "Last 5 nodes heard:\n" + str("\n".join(node_list))

elif "whereami" in message:
bot_response = self.where_am_i(location[0], location[1])

elif "joke" in message:
bot_response = self.tell_joke()

return bot_response

def tell_joke(self):
# tell a dad joke, does it need an explanationn :)
dadjoke = Dadjoke()
return dadjoke.joke

def where_am_i(self, lat=0, lon=0):
whereIam = ""
if float(lat) == 0 and float(lon) == 0:
return super().NO_DATA_NOGPS
# initialize Nominatim API
return self.NO_DATA_NOGPS

geolocator = Nominatim(user_agent="mesh-bot")

location = geolocator.reverse(lat + ", " + lon)
address = location.raw['address']
address_components = ['house_number', 'road', 'city', 'state', 'postcode', 'county', 'country']
whereIam += ' '.join([address.get(component, '') for component in address_components if component in address])
location = geolocator.reverse(str(lat) + ", " + str(lon))
address = location.raw["address"]
address_components = [
"house_number",
"road",
"city",
"state",
"postcode",
"county",
"country",
]
whereIam += " ".join(
[
address.get(component, "")
for component in address_components
if component in address
]
)
grid = mh.to_maiden(float(lat), float(lon))
whereIam += " Grid: " + grid

return whereIam
return whereIam

def get_node_list(self, limit=5):

result = []

for index, node in enumerate(self.node_list):
# ignore own
if node["num"] == self.myNodeNum:
continue

node_name = MessageProcessor.get_name_from_number(
self.interface, node["num"]
)
snr = node.get("snr", 0)

# issue where lastHeard is not always present
last_heard = node.get("lastHeard", 0)

# make a list of nodes with last heard time and SNR
item = (node_name, last_heard, snr)
result.append(item)

if index >= limit:
break

result.sort(key=lambda x: x[1], reverse=True)

return result
94 changes: 94 additions & 0 deletions db_operations.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,94 @@
import sqlite3
import threading
from datetime import datetime

thread_local = threading.local()


def get_db_connection():
if not hasattr(thread_local, "connection"):
thread_local.connection = sqlite3.connect(database="messages.db", check_same_thread=False)
return thread_local.connection


def initialize_database():
conn = get_db_connection()
c = conn.cursor()

c.execute(
"""CREATE TABLE IF NOT EXISTS message (
id INTEGER PRIMARY KEY AUTOINCREMENT,
message_id INTEGER NOT NULL,
sender TEXT NOT NULL,
sender_short_name TEXT NOT NULL,
sender_long_name TEXT NOT NULL,
reply_id INTEGER NOT NULL,
channel INTEGER NOT NULL,
date TEXT NOT NULL,
content TEXT NOT NULL
);"""
)
c.execute(
"""CREATE TABLE IF NOT EXISTS channels (
id INTEGER PRIMARY KEY AUTOINCREMENT,
name TEXT NOT NULL,
url TEXT NOT NULL
);"""
)
conn.commit()
print("Database schema initialized.")


def add_channel(name, url):
conn = get_db_connection()
c = conn.cursor()
c.execute("INSERT INTO channels (name, url) VALUES (?, ?)", (name, url))
conn.commit()


def get_channels():
conn = get_db_connection()
c = conn.cursor()
c.execute("SELECT name, url FROM channels")
return c.fetchall()


def add_message(
message_id,
sender_id,
sender_short_name,
sender_long_name,
reply_id,
channel,
content,
):
conn = get_db_connection()
c = conn.cursor()
date = datetime.now().strftime("%Y-%m-%d %H:%M")

c.execute(
"INSERT INTO message (message_id, sender, sender_short_name, sender_long_name, reply_id, "
"channel, date, content) VALUES (?, ?, ?, ?, ?, ?, ?, ?)",
(
message_id,
sender_id,
sender_short_name,
sender_long_name,
reply_id,
channel,
date,
content,
),
)
return conn.commit()


def get_messages(top: int = 5):
conn = get_db_connection()
c = conn.cursor()
c.execute(
"SELECT id, sender_short_name, sender_long_name, date, channel, "
"content FROM message ORDER BY date DESC LIMIT ?",
(top,),
)
return c.fetchall()
3 changes: 2 additions & 1 deletion log.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
from datetime import datetime


def log_timestamp():
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
return datetime.now().strftime("%Y-%m-%d %H:%M:%S")
45 changes: 28 additions & 17 deletions mesh_bot.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,52 +5,63 @@
import time
from typing import List
from pubsub import pub
from datetime import datetime
from basic_bot import BasicBot
from db_operations import initialize_database
from message_processor import MessageProcessor
from serial_mesh import SerialMeshHelper

import meshtastic.serial_interface
import meshtastic.tcp_interface
import meshtastic.ble_interface

from store_forward_bot import StoreForwardBot
from weather_bot import WeatherBot

# Uncomment the interface you want to use depending on your device connection
interface = meshtastic.serial_interface.SerialInterface() #serial interface
#interface=meshtastic.tcp_interface.TCPInterface(hostname="10.0.4.36") # IP of your device
#interface=meshtastic.ble_interface.BLEInterface("10:06:1C:49:90:36") # BLE interface - find it using meshtastic --ble-scan
interface = meshtastic.serial_interface.SerialInterface() # serial interface
# interface=meshtastic.tcp_interface.TCPInterface(hostname="10.0.4.36") # IP of your device
# BLE interface - find it using meshtastic --ble-scan
# interface=meshtastic.ble_interface.BLEInterface("10:06:1C:49:90:36")

#interface = None
initialize_database()

myinfo = interface.getMyNodeInfo()
myNodeNum = myinfo['num']
print(f"System: My Node Number is {myNodeNum}")
bb = BasicBot(interface)
wb = WeatherBot(interface)
sfb = StoreForwardBot(interface)

bb = BasicBot()
wb = WeatherBot()
# node_list = interface.nodes.values()
# print(f"System: Node List {node_list}")

message_processors:List[MessageProcessor] = [bb, wb]
message_processors: List[MessageProcessor] = [bb, wb, sfb]
sh = SerialMeshHelper(interface, message_processors)

# if not serial or serial.myNodeNum == -1:
# print("System: Critical Error script abort. Could not get myNodeNum")
# exit()


def exit_handler(signum, frame):
print("\nSystem: Closing Autoresponder")
interface.close()
exit (0)
exit(0)


print("\nMeshtastic Autoresponder MESH Bot CTL+C to exit\n")

print ("\nMeshtastic Autoresponder MESH Bot CTL+C to exit\n")
pub.subscribe(sh.onReceive, 'meshtastic.receive')
print (f"System: Autoresponder Started for device {sh.get_name_from_number(sh.myNodeNum)}")
# subscribe to process messages
pub.subscribe(sh.onReceive, "meshtastic.receive")
# subscrie to store to DB
pub.subscribe(sfb.onReceive, "meshtastic.receive")

print(
f"System: Autoresponder Started for device {MessageProcessor.get_name_from_number(interface, sh.myNodeNum)}"
)

while True:
# Catch CTL+C to exit
time.sleep(0.05)
signal.signal(signal.SIGINT, exit_handler)
time.sleep(0.05)
pass



# EOF
Loading
Loading