Compare commits

...

51 Commits

Author SHA1 Message Date
SpudGunMan
ad123dc93c schedule 2024-08-27 16:58:06 -07:00
SpudGunMan
22983133ee Update mesh_bot.py 2024-08-27 16:44:22 -07:00
SpudGunMan
60c4a885fd Revert "Update mesh_bot.py"
This reverts commit 95d6d7b7d5.
2024-08-27 16:39:16 -07:00
SpudGunMan
95d6d7b7d5 Update mesh_bot.py 2024-08-27 16:24:44 -07:00
SpudGunMan
37a86b7e2b Update system.py 2024-08-27 16:19:52 -07:00
SpudGunMan
c4ef1251c9 enhance code with inital brodcaster
https://github.com/SpudGunMan/meshing-around/issues/51 referenced in this enhancement. this is partially implemented for now in code
2024-08-27 16:06:52 -07:00
SpudGunMan
9d7e42aa60 onDisconnect
add monitor for ondisconnect
2024-08-27 13:08:59 -07:00
SpudGunMan
8536e354ad Update locationdata.py 2024-08-23 22:29:08 -07:00
SpudGunMan
e3faf676cd Update system.py 2024-08-23 22:24:04 -07:00
SpudGunMan
630e016805 Update locationdata.py 2024-08-23 22:24:00 -07:00
SpudGunMan
23b8b8135c Update system.py 2024-08-21 23:13:50 -07:00
SpudGunMan
7f0b4c079a Update README.md 2024-08-21 22:56:42 -07:00
SpudGunMan
47649cdedc Update system.py 2024-08-21 22:48:44 -07:00
SpudGunMan
7915798ca2 Update system.py 2024-08-21 22:46:58 -07:00
SpudGunMan
86cd88910a Update system.py 2024-08-21 22:13:21 -07:00
SpudGunMan
229ccc75f0 Update log.py 2024-08-21 22:00:56 -07:00
SpudGunMan
6f3e3a7957 Update system.py 2024-08-21 21:54:51 -07:00
SpudGunMan
1f1996b909 Update locationdata.py 2024-08-21 21:50:03 -07:00
SpudGunMan
c2069da919 Update locationdata.py 2024-08-21 21:49:29 -07:00
SpudGunMan
458957ddfb ohmyglob 2024-08-21 21:45:12 -07:00
SpudGunMan
95c266fbf3 typo 2024-08-21 21:43:58 -07:00
SpudGunMan
4857940165 Update mesh_bot.py 2024-08-21 21:41:05 -07:00
SpudGunMan
4c780d09e7 fix 2024-08-21 21:40:17 -07:00
SpudGunMan
d616867cd1 Update mesh_bot.py 2024-08-21 21:38:27 -07:00
SpudGunMan
909c4ad3bc Update locationdata.py 2024-08-21 21:31:58 -07:00
SpudGunMan
44eff643a9 Update locationdata.py 2024-08-21 21:27:32 -07:00
SpudGunMan
a223e57690 Update system.py 2024-08-21 20:04:16 -07:00
SpudGunMan
69bf2d7081 enhance sentry with expire out old records
choosing to resolve https://github.com/SpudGunMan/meshing-around/issues/47 with filtering out after 24 hours
2024-08-21 19:37:37 -07:00
SpudGunMan
c64644a331 enhance 2024-08-21 18:32:30 -07:00
SpudGunMan
e8b82ca687 fixes
why did I do it like this..
2024-08-21 18:04:54 -07:00
SpudGunMan
47bd8d1d26 HeartbeatCleanup
Better Code for more secure operations, dropping OS and SYS modules and using a built in. requires Python 3.4 at least for this function.
2024-08-21 17:47:42 -07:00
SpudGunMan
a6e88a63d5 syslog2disk
resolve https://github.com/SpudGunMan/meshing-around/issues/49
2024-08-21 17:30:28 -07:00
SpudGunMan
e6be9a7d13 Update locationdata.py
fix https://github.com/SpudGunMan/meshing-around/issues/44
2024-08-18 09:07:14 -07:00
SpudGunMan
8e34925af7 Update locationdata.py 2024-08-18 01:41:17 -07:00
SpudGunMan
1ec6cefc16 Update mesh_bot.py 2024-08-18 01:26:53 -07:00
SpudGunMan
4a4c5c3e0f Update install.sh 2024-08-17 23:50:08 -07:00
SpudGunMan
19e6a38355 Update install.sh 2024-08-17 23:46:32 -07:00
SpudGunMan
066f451a4d orderLogs 2024-08-16 02:37:38 -07:00
SpudGunMan
c50776b991 aarg 2024-08-15 23:25:31 -07:00
SpudGunMan
8daa9f71e2 fixTypo 2024-08-15 22:52:30 -07:00
SpudGunMan
340cff5e5b Update log.py
fix writing to disk when not wanted
2024-08-14 19:05:28 -07:00
SpudGunMan
1747125ea7 enhance 2024-08-14 18:33:39 -07:00
SpudGunMan
6ce650dc15 Update mesh_bot.py 2024-08-14 17:56:34 -07:00
SpudGunMan
d2b303b47c Update system.py 2024-08-14 17:52:57 -07:00
SpudGunMan
74c5bfa64b Update system.py 2024-08-14 14:47:43 -07:00
SpudGunMan
f826c0e4bb Update locationdata.py 2024-08-14 13:26:28 -07:00
SpudGunMan
b8fc3c6c37 Update system.py 2024-08-14 12:07:44 -07:00
SpudGunMan
22b8c8a62e Update system.py 2024-08-13 16:18:57 -07:00
SpudGunMan
f7ad83d2b5 Update system.py 2024-08-13 16:09:46 -07:00
SpudGunMan
fa8b5d6b71 comments 2024-08-13 16:02:13 -07:00
SpudGunMan
036bff1489 Update system.py 2024-08-13 15:51:41 -07:00
12 changed files with 235 additions and 105 deletions

View File

@@ -141,8 +141,22 @@ signalHoldTime = 10
signalCooldown = 5
signalCycleLimit = 5
```
Logging messages to disk or Syslog to disk uses the python native logging fuction. Take a look at the [/modules/log.py](/modules/log.py) you can set the file logger for syslog to INFO for example to not log DEBUG messages to file log, or modify the stdOut level.
```
[general]
# logging to file of the non Bot messages
LogMessagesToFile = True
# Logging of system messages to file
SyslogToFile = True
*log.py
file_handler.setLevel(logging.INFO) # DEBUG used by default for system logs to disk example here shows INFO
```
# requirements
can also be installed with `pip install -r requirements.txt`
Python 3.4 and likely higher is needed, developed on latest release.
The following can also be installed with `pip install -r requirements.txt` or using the install.sh script for venv and automation
```
pip install meshtastic
@@ -158,6 +172,7 @@ pip install maidenhead
pip install beautifulsoup4
pip install dadjokes
pip install geopy
pip install schedule
```
The following is needed for open-meteo use
```

View File

@@ -43,6 +43,8 @@ zuluTime = False
urlTimeout = 10
# logging to file of the non Bot messages
LogMessagesToFile = False
# Logging of system messages to file
SyslogToFile = False
[sentry]

View File

@@ -7,13 +7,21 @@ try:
with open('../bbsdb.pkl', 'rb') as f:
bbs_messages = pickle.load(f)
except:
print ("\nSystem: bbsdb.pkl not found")
try:
with open('bbsdb.pkl', 'rb') as f:
bbs_messages = pickle.load(f)
except:
print ("\nSystem: bbsdb.pkl not found")
try:
with open('../bbsdm.pkl', 'rb') as f:
bbs_dm = pickle.load(f)
except:
print ("\nSystem: bbsdm.pkl not found")
try:
with open('bbsdm.pkl', 'rb') as f:
bbs_dm = pickle.load(f)
except:
print ("\nSystem: bbsdm.pkl not found")
print ("\nSystem: bbs_messages")
print (bbs_messages)

View File

@@ -44,6 +44,7 @@ if [ $venv == "y" ]; then
# install dependencies
pip install -U -r requirements.txt
fi
else
printf "\nSkipping virtual environment...\n"
# install dependencies
@@ -93,7 +94,9 @@ if [ $bot == "n" ]; then
if [ -f launch.sh ]; then
printf "\nTo run the bot, use the command: ./launch.sh\n"
./launch.sh
fi
fi
echo "Goodbye!"
printf "\nGoodbye!"
exit 0

View File

@@ -18,8 +18,8 @@ def auto_response(message, snr, rssi, hop, message_from_id, channel_number, devi
"pong": lambda: "🏓PING!!",
"motd": lambda: handle_motd(message),
"bbshelp": bbs_help,
"wxalert": lambda: handle_wxalert(message_from_id, deviceID),
"wxa": lambda: handle_wxalert(message_from_id, deviceID),
"wxalert": lambda: handle_wxalert(message_from_id, deviceID, message),
"wxa": lambda: handle_wxalert(message_from_id, deviceID, message),
"wxc": lambda: handle_wxc(message_from_id, deviceID, 'wxc'),
"wx": lambda: handle_wxc(message_from_id, deviceID, 'wx'),
"joke": tell_joke,
@@ -50,7 +50,7 @@ def auto_response(message, snr, rssi, hop, message_from_id, channel_number, devi
if len(cmds) > 0:
# sort the commands by index value
cmds = sorted(cmds, key=lambda k: k['index'])
logger.debug(f"System: Bot Detected: {cmds}")
logger.debug(f"System: Bot detected Commands:{cmds}")
# run the first command after sorting
bot_response = command_handler[cmds[0]['cmd']]()
@@ -80,12 +80,17 @@ def handle_motd(message):
else:
return MOTD
def handle_wxalert(message_from_id, deviceID):
def handle_wxalert(message_from_id, deviceID, message):
if use_meteo_wxApi:
return "wxalert is not supported"
else:
location = get_node_location(message_from_id, deviceID)
weatherAlert = getActiveWeatherAlertsDetail(str(location[0]), str(location[1]))
if "wxalert" in message:
# Detailed weather alert
weatherAlert = getActiveWeatherAlertsDetail(str(location[0]), str(location[1]))
else:
weatherAlert = getWeatherAlerts(str(location[0]), str(location[1]))
return weatherAlert
def handle_wxc(message_from_id, deviceID, cmd):
@@ -122,8 +127,6 @@ def handle_bbspost(message, message_from_id, deviceID):
toNode = get_num_from_short_name(toNode, deviceID)
if toNode == 0:
return "Node not found " + message.split("@")[1].split("#")[0]
else:
logger.debug(f"System: bbspost, name lookup found: {toNode}")
if "#" in message:
body = message.split("#")[1]
return bbs_post_dm(toNode, body, message_from_id)
@@ -198,6 +201,25 @@ def handle_testing(hop, snr, rssi):
else:
return "🏓Testing 1,2,3 " + hop
def onDisconnect(interface):
global retry_int1, retry_int2
rxType = type(interface).__name__
if rxType == 'SerialInterface':
rxInterface = interface.__dict__.get('devPath', 'unknown')
logger.critical(f"System: Lost Connection to Device {rxInterface}")
if port1 in rxInterface:
retry_int1 = True
elif interface2_enabled and port2 in rxInterface:
retry_int2 = True
if rxType == 'TCPInterface':
rxHost = interface.__dict__.get('hostname', 'unknown')
logger.critical(f"System: Lost Connection to Device {rxHost}")
if hostname1 in rxHost and interface1_type == 'tcp':
retry_int1 = True
elif interface2_enabled and hostname2 in rxHost and interface2_type == 'tcp':
retry_int2 = True
def onReceive(packet, interface):
# extract interface defailts from interface object
rxType = type(interface).__name__
@@ -326,7 +348,7 @@ def onReceive(packet, interface):
send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, 0, rxNode)
else:
# message is not for bot to respond to
# ignore the message but add it to the message history and repeat it if enabled
# ignore the message but add it to the message history list
if zuluTime:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
else:
@@ -337,10 +359,15 @@ def onReceive(packet, interface):
else:
msg_history.pop(0)
msg_history.append((get_name_from_number(message_from_id, 'long', rxNode), message_string, channel_number, timestamp, rxNode))
# check if repeater is enabled and the other interface is enabled
# print the message to the log and sdout
logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Ignoring Message:" + CustomFormatter.white +\
f" {message_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}")
if log_messages_to_file:
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
# repeat the message on the other device
if repeater_enabled and interface2_enabled:
# repeat the message on the other device
# wait a 700ms to avoid message collision from lora-ack.
time.sleep(0.7)
rMsg = (f"{message_string} From:{get_name_from_number(message_from_id, 'short', rxNode)}")
@@ -352,12 +379,6 @@ def onReceive(packet, interface):
elif rxNode == 2:
logger.debug(f"Repeating message on Device1 Channel:{channel_number}")
send_message(rMsg, channel_number, 0, 1)
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
else:
# nothing to do for us
logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Ignoring Message:" + CustomFormatter.white +\
f" {message_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}")
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
except KeyError as e:
logger.critical(f"System: Error processing packet: {e} Device:{rxNode}")
print(packet) # print the packet for debugging
@@ -367,6 +388,7 @@ async def start_rx():
print (CustomFormatter.bold_white + f"\nMeshtastic Autoresponder Bot CTL+C to exit\n" + CustomFormatter.reset)
# Start the receive subscriber using pubsub via meshtastic library
pub.subscribe(onReceive, 'meshtastic.receive')
pub.subscribe(onDisconnect, 'meshtastic.connection.lost')
logger.info(f"System: Autoresponder Started for Device1 {get_name_from_number(myNodeNum1, 'long', 1)},"
f"{get_name_from_number(myNodeNum1, 'short', 1)}. NodeID: {myNodeNum1}, {decimal_to_hex(myNodeNum1)}")
if interface2_enabled:
@@ -374,6 +396,8 @@ async def start_rx():
f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}")
if log_messages_to_file:
logger.debug(f"System: Logging Messages to disk")
if syslog_to_file:
logger.debug(f"System: Logging System Logs to disk")
if bbs_enabled:
logger.debug(f"System: BBS Enabled, {bbsdb} has {len(bbs_messages)} messages. Direct Mail Messages waiting: {(len(bbs_dm) - 1)}")
if solar_conditions_enabled:
@@ -397,6 +421,22 @@ async def start_rx():
logger.debug(f"System: Repeater Enabled for Channels: {repeater_channels}")
if radio_dectection_enabled:
logger.debug(f"System: Radio Detection Enabled using rigctld at {rigControlServerAddress} brodcasting to channels: {sigWatchBrodcastCh} for {get_freq_common_name(get_hamlib('f'))}")
if scheduler_enabled:
# Examples of using the scheduler, Times here are in 24hr format
# https://schedule.readthedocs.io/en/stable/
# Good Morning Every day at 09:00 using send_message function to channel 2 on device 1
#schedule.every().day.at("09:00").do(lambda: send_message("Good Morning", 2, 0, 1))
# Send WX every Morning at 08:00 using handle_wxc function to channel 2 on device 1
#schedule.every().day.at("08:00").do(lambda: send_message(handle_wxc(0, 1, 'wx'), 2, 0, 1))
# Send a Net Starting Now Message Every Wednesday at 19:00 using send_message function to channel 2 on device 1
#schedule.every().wednesday.at("19:00").do(lambda: send_message("Net Starting Now", 2, 0, 1)
#
logger.debug("System: Starting the broadcast scheduler")
await BroadcastScheduler()
# here we go loopty loo
while True:

View File

@@ -1,4 +1,4 @@
# helper functions to use location data
# helper functions to use location data like NOAA weather
# K7MHI Kelly Keeton 2024
import json # pip install json
@@ -15,8 +15,8 @@ def where_am_i(lat=0, lon=0):
whereIam = ""
grid = mh.to_maiden(float(lat), float(lon))
if float(lat) == 0 and float(lon) == 0:
logger.error("Location: No GPS data, cant find where you are")
if int(float(lat)) == 0 and int(float(lon)) == 0:
logger.error("Location: No GPS data, try sending location")
return NO_DATA_NOGPS
# initialize Nominatim API
@@ -42,7 +42,7 @@ def where_am_i(lat=0, lon=0):
def get_tide(lat=0, lon=0):
station_id = ""
if float(lat) == 0 and float(lon) == 0:
logger.error("Location:No GPS data, cant find where you are for tide")
logger.error("Location:No GPS data, try sending location for tide")
return NO_DATA_NOGPS
station_lookup_url = "https://api.tidesandcurrents.noaa.gov/mdapi/prod/webapi/tidepredstations.json?lat=" + str(lat) + "&lon=" + str(lon) + "&radius=50"
try:
@@ -192,7 +192,9 @@ def abbreviate_weather(row):
"West": "W",
"precipitation": "precip",
"showers": "shwrs",
"thunderstorms": "t-storms"
"thunderstorms": "t-storms",
"quarters": "qtrs",
"quarter": "qtr"
}
line = row
@@ -209,14 +211,15 @@ def getWeatherAlerts(lat=0, lon=0):
alert_url = "https://api.weather.gov/alerts/active.atom?point=" + str(lat) + "," + str(lon)
#alert_url = "https://api.weather.gov/alerts/active.atom?area=WA"
#logger.debug("Location:Fetching weather alerts from NOAA for " + str(lat) + ", " + str(lon))
try:
alert_data = requests.get(alert_url, timeout=urlTimeoutSeconds)
if not alert_data.ok:
logger.error("Location:Error fetching weather alerts from NOAA")
logger.warning("Location:Error fetching weather alerts from NOAA")
return ERROR_FETCHING_DATA
except (requests.exceptions.RequestException):
logger.error("Location:Error fetching weather alerts from NOAA")
logger.warning("Location:Error fetching weather alerts from NOAA")
return ERROR_FETCHING_DATA
alerts = ""
@@ -248,19 +251,20 @@ def getActiveWeatherAlertsDetail(lat=0, lon=0):
# get the latest details of weather alerts from NOAA
alerts = ""
if float(lat) == 0 and float(lon) == 0:
logger.error("Location:No GPS data, cant find where you are for weather alerts")
logger.warning("Location:No GPS data, try sending location for weather alerts")
return NO_DATA_NOGPS
alert_url = "https://api.weather.gov/alerts/active.atom?point=" + str(lat) + "," + str(lon)
#alert_url = "https://api.weather.gov/alerts/active.atom?area=WA"
#logger.debug("Location:Fetching weather alerts detailed from NOAA for " + str(lat) + ", " + str(lon))
try:
alert_data = requests.get(alert_url, timeout=urlTimeoutSeconds)
if not alert_data.ok:
logger.error("Location:Error fetching weather alerts detailed from NOAA")
logger.warning("Location:Error fetching weather alerts from NOAA")
return ERROR_FETCHING_DATA
except (requests.exceptions.RequestException):
logger.error("Location:Error fetching weather alerts detailed from NOAA")
logger.warning("Location:Error fetching weather alerts from NOAA")
return ERROR_FETCHING_DATA
alerts = ""

View File

@@ -1,3 +1,7 @@
# Custom logger for MeshBot and PongBot
# you can change the sdtout_handler level to logging.INFO to only show INFO level logs
# stdout_handler.setLevel(logging.INFO)vs stdout_handler.setLevel(logging.DEBUG)
# 2024 Kelly Keeton K7MHI
import logging
from datetime import datetime
from modules.settings import *
@@ -42,6 +46,7 @@ msgLogger.propagate = False
# Define format for logs
logFormat = '%(asctime)s | %(levelname)8s | %(message)s'
msgLogFormat = '%(asctime)s | %(message)s'
today = datetime.now()
# Create stdout handler for logging to the console
stdout_handler = logging.StreamHandler()
@@ -50,13 +55,18 @@ stdout_handler.setLevel(logging.DEBUG)
# Set format for stdout handler
stdout_handler.setFormatter(CustomFormatter(logFormat))
# Create file handler for logging to a file
today = datetime.now()
file_handler = logging.FileHandler('messages{}.log'.format(today.strftime('%Y_%m_%d')))
file_handler.setLevel(logging.INFO)
file_handler.setFormatter(logging.Formatter(msgLogFormat))
# Add handlers to the logger
logger.addHandler(stdout_handler)
if syslog_to_file:
# Create file handler for logging to a file
file_handler = logging.FileHandler('system{}.log'.format(today.strftime('%Y_%m_%d')))
file_handler.setLevel(logging.DEBUG) # DEBUG used by default for system logs to disk
file_handler.setFormatter(logging.Formatter(logFormat))
logger.addHandler(file_handler)
if log_messages_to_file:
# Create file handler for logging to a file
file_handler = logging.FileHandler('messages{}.log'.format(today.strftime('%Y_%m_%d')))
file_handler.setLevel(logging.INFO) # INFO used for messages to disk
file_handler.setFormatter(logging.Formatter(msgLogFormat))
msgLogger.addHandler(file_handler)

View File

@@ -1,3 +1,5 @@
# Settings for MeshBot and PongBot
# 2024 Kelly Keeton K7MHI
import configparser
# messages
@@ -22,6 +24,7 @@ max_retry_count1 = 4 # max retry count for interface 1
max_retry_count2 = 4 # max retry count for interface 2
retry_int1 = False
retry_int2 = False
scheduler_enabled = False # enable the scheduler currently config via code only
# Read the config file, if it does not exist, create basic config file
config = configparser.ConfigParser()
@@ -82,6 +85,7 @@ try:
publicChannel = config['general'].getint('defaultChannel', 0) # the meshtastic public channel
zuluTime = config['general'].getboolean('zuluTime', False) # aka 24 hour time
log_messages_to_file = config['general'].getboolean('LogMessagesToFile', True) # default True
syslog_to_file = config['general'].getboolean('SyslogToFile', False) # default True
urlTimeoutSeconds = config['general'].getint('urlTimeout', 10) # default 10 seconds
store_forward_enabled = config['general'].getboolean('StoreForward', True) # default False
storeFlimit = config['general'].getint('StoreLimit', 3) # default 3 messages for S&F

View File

@@ -1,5 +1,5 @@
# helper functions to get HF band conditions, DRAP X-ray flux, and sunrise/sunset times
# some code from https://github.com/Murturtle/MeshLink
# HF code from https://github.com/Murturtle/MeshLink
# K7MHI Kelly Keeton 2024
import requests # pip install requests

View File

@@ -1,4 +1,4 @@
# helper functions for system related tasks
# helper functions and init for system related tasks
# K7MHI Kelly Keeton 2024
import meshtastic.serial_interface #pip install meshtastic
@@ -6,6 +6,7 @@ import meshtastic.tcp_interface
import meshtastic.ble_interface
import time
import asyncio
import contextlib # for suppressing output on watchdog
from modules.log import *
# Global Variables
@@ -63,6 +64,13 @@ if dad_jokes_enabled:
trap_list = trap_list + ("joke",)
help_message = help_message + ", joke"
# Scheduled Broadcast Configuration
if scheduler_enabled:
import schedule # pip install schedule
# Reminder Scheduler is enabled every Monday at noon send a log message
schedule.every().monday.at("12:00").do(lambda: logger.info("System: Scheduled Broadcast Reminder"))
# Sentry Configuration
if sentry_enabled:
from math import sqrt
import geopy.distance # pip install geopy
@@ -125,6 +133,8 @@ if interface2_enabled:
else:
myNodeNum2 = 777
# functions below
def decimal_to_hex(decimal_number):
return f"!{decimal_number:08x}"
@@ -166,25 +176,33 @@ def get_num_from_short_name(short_name, nodeInt=1):
logger.debug(f"System: Getting Node Number from Short Name: {short_name} on Device: {nodeInt}")
if nodeInt == 1:
for node in interface1.nodes.values():
if str(short_name.lower()) == node['user']['shortName'].lower():
#logger.debug(f"System: Checking Node: {node['user']['shortName']} against {short_name} for number {node['num']}")
if short_name == node['user']['shortName']:
return node['num']
elif str(short_name.lower()) == node['user']['shortName'].lower():
return node['num']
else:
# try other interface
if interface2_enabled:
for node in interface2.nodes.values():
if str(short_name.lower()) == node['user']['shortName'].lower():
if short_name == node['user']['shortName']:
return node['num']
elif str(short_name.lower()) == node['user']['shortName'].lower():
return node['num']
if nodeInt == 2:
for node in interface2.nodes.values():
if str(short_name.lower()) == node['user']['shortName'].lower():
if short_name == node['user']['shortName']:
return node['num']
elif str(short_name.lower()) == node['user']['shortName'].lower():
return node['num']
else:
# try other interface
if interface2_enabled:
for node in interface1.nodes.values():
if str(short_name.lower()) == node['user']['shortName'].lower():
if short_name == node['user']['shortName']:
return node['num']
elif str(short_name.lower()) == node['user']['shortName'].lower():
return node['num']
return 0
def get_node_list(nodeInt=1):
@@ -193,6 +211,7 @@ def get_node_list(nodeInt=1):
node_list1 = []
node_list2 = []
short_node_list = []
last_heard = 0
if nodeInt == 1:
if interface1.nodes:
for node in interface1.nodes.values():
@@ -219,10 +238,8 @@ def get_node_list(nodeInt=1):
node_name = get_name_from_number(node['num'], 'long', nodeInt)
snr = node.get('snr', 0)
# issue where lastHeard is not always present, also had issues with None
# issue where lastHeard is not always present
last_heard = node.get('lastHeard', 0)
if last_heard is None:
last_heard = 0
# make a list of nodes with last heard time and SNR
item = (node_name, last_heard, snr)
@@ -233,13 +250,15 @@ def get_node_list(nodeInt=1):
try:
#print (f"Node List: {node_list1[:5]}\n")
node_list1.sort(key=lambda x: x[1], reverse=True)
node_list1.sort(key=lambda x: x[1] if x[1] is not None else 0, reverse=True)
#print (f"Node List: {node_list1[:5]}\n")
node_list2.sort(key=lambda x: x[1], reverse=True)
if interface2_enabled:
node_list2.sort(key=lambda x: x[1] if x[1] is not None else 0, reverse=True)
except Exception as e:
logger.error(f"System: Error sorting node list: {e}")
#print (f"Node List1: {node_list1[:5]}\n")
#print (f"Node List2: {node_list2[:5]}\n")
logger.debug(f"Node List1: {node_list1[:5]}\n")
if interface2_enabled:
logger.debug(f"Node List2: {node_list2[:5]}\n")
node_list = ERROR_FETCHING_DATA
try:
@@ -263,6 +282,7 @@ def get_node_location(number, nodeInt=1, channel=0):
latitude = latitudeValue
longitude = longitudeValue
position = [latitudeValue,longitudeValue]
lastheard = 0
if nodeInt == 1:
if interface1.nodes:
for node in interface1.nodes.values():
@@ -278,17 +298,15 @@ def get_node_location(number, nodeInt=1, channel=0):
return position
else:
logger.warning(f"System: No location data for {number} using default location")
# request location data
try:
logger.debug(f"System: Requesting location data for {number}")
if nodeInt == 1:
interface1.sendPosition(destinationId=number, wantResponse=False, channelIndex=channel)
if nodeInt == 2:
interface2.sendPosition(destinationId=number, wantResponse=False, channelIndex=channel)
except Exception as e:
logger.error(f"System: Error requesting location data for {number}. Error: {e}")
# try:
# logger.debug(f"System: Requesting location data for {number}")
# if nodeInt == 1:
# interface1.sendPosition(destinationId=number, wantResponse=False, channelIndex=channel)
# if nodeInt == 2:
# interface2.sendPosition(destinationId=number, wantResponse=False, channelIndex=channel)
# except Exception as e:
# logger.error(f"System: Error requesting location data for {number}. Error: {e}")
return position
else:
logger.warning(f"System: No nodes found")
@@ -312,9 +330,11 @@ def get_node_location(number, nodeInt=1, channel=0):
else:
logger.warning(f"System: No nodes found")
return position
return position
def get_closest_nodes(nodeInt=1,returnCount=3):
node_list = []
if nodeInt == 1:
if interface1.nodes:
for node in interface1.nodes.values():
@@ -324,13 +344,18 @@ def get_closest_nodes(nodeInt=1,returnCount=3):
latitude = node['position']['latitude']
longitude = node['position']['longitude']
# set radius around BOT position
distance = round(geopy.distance.geodesic((latitudeValue, longitudeValue), (latitude, longitude)).m, 2)
#lastheard time in unix time
lastheard = node.get('lastHeard', 0)
#if last heard is over 24 hours ago, ignore the node
if lastheard < (time.time() - 86400):
continue
# Calculate distance to node from config.ini location
distance = round(geopy.distance.geodesic((latitudeValue, longitudeValue), (latitude, longitude)).m, 2)
if (distance < sentry_radius):
if nodeID != myNodeNum1 and myNodeNum2 and str(nodeID) not in sentryIgnoreList:
node_list.append({'id': nodeID, 'latitude': latitude, 'longitude': longitude, 'distance': distance})
# calculate distance to node and report
except Exception as e:
pass
@@ -350,6 +375,7 @@ def get_closest_nodes(nodeInt=1,returnCount=3):
else:
logger.error(f"System: No nodes found in closest_nodes on interface {nodeInt}")
return ERROR_FETCHING_DATA
if nodeInt == 2:
if interface2.nodes:
for node in interface2.nodes.values():
@@ -359,20 +385,23 @@ def get_closest_nodes(nodeInt=1,returnCount=3):
latitude = node['position']['latitude']
longitude = node['position']['longitude']
# set radius around BOT position
distance = geopy.distance.geodesic((latitudeValue, longitudeValue), (latitude, longitude)).m
#lastheard time in unix time
lastheard = node.get('lastHeard', 0)
#if last heard is over 24 hours ago, ignore the node
if lastheard < (time.time() - 86400):
continue
# Calculate distance to node from config.ini location
distance = round(geopy.distance.geodesic((latitudeValue, longitudeValue), (latitude, longitude)).m, 2)
if (distance < sentry_radius):
if nodeID != myNodeNum1 and myNodeNum2 and str(nodeID) not in sentryIgnoreList:
node_list.append({'id': nodeID, 'latitude': latitude, 'longitude': longitude, 'distance': distance})
# calculate distance to node and report
except Exception as e:
pass
#sort by distance closest to lattitudeValue, longitudeValue
node_list.sort(key=lambda x: (x['latitude']-latitudeValue)**2 + (x['longitude']-longitudeValue)**2)
# sort by distance closest
node_list.sort(key=lambda x: x['distance'])
# return the first 3 closest nodes by default
return node_list[:returnCount]
else:
@@ -395,7 +424,7 @@ def send_message(message, ch, nodeid=0, nodeInt=1):
for word in split_message:
if len(line + word) < MESSAGE_CHUNK_SIZE:
if word == 'NEWLINE':
if 'NEWLINE' in word or '\n' in word or '\r' in word:
# chunk by newline if it exists
message_list.append(line)
line = ''
@@ -406,10 +435,11 @@ def send_message(message, ch, nodeid=0, nodeInt=1):
line = word + ' '
message_list.append(line) # needed add contents of the last 'line' into the list
message_list = [m.replace('NEWLINE', '') for m in message_list]
for m in message_list:
if nodeid == 0:
#Send to channel
# Send to channel
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + "Sending Multi-Chunk Message: " + CustomFormatter.white + m.replace('\n', ' '))
if nodeInt == 1:
interface1.sendText(text=m, channelIndex=ch)
@@ -486,6 +516,12 @@ def exit_handler():
asyncLoop.close()
exit (0)
async def BroadcastScheduler():
# handle schedule checks for the broadcast of messages
while True:
schedule.run_pending()
await asyncio.sleep(1)
async def handleSignalWatcher():
global lastHamLibAlert, antiSpam, sigWatchBrodcastCh
# monitor rigctld for signal strength and frequency
@@ -580,22 +616,6 @@ async def retry_interface(nodeID=1):
except Exception as e:
logger.error(f"System: opening interface2: {e}")
# this is a workaround because .localNode.getMetadata spits out a lot of debug info which cant be suppressed
from contextlib import contextmanager
import os
import sys
@contextmanager
def suppress_stdout():
with open(os.devnull, "w") as devnull:
old_stdout = sys.stdout
sys.stdout = devnull
try:
yield
finally:
sys.stdout = old_stdout
async def watchdog():
global retry_int1, retry_int2
if sentry_enabled:
@@ -611,8 +631,10 @@ async def watchdog():
#print(f"MeshBot System: watchdog running\r", end="")
if interface1 is not None and not retry_int1:
try:
with suppress_stdout():
# this is a workaround because .localNode.getMetadata spits out a lot of debug info which cant be suppressed
with contextlib.redirect_stdout(None):
interface1.localNode.getMetadata()
print(f"System: if you see this upgrade python to >3.4")
#if "device_state_version:" not in meta:
except Exception as e:
logger.error(f"System: communicating with interface1, trying to reconnect: {e}")
@@ -652,8 +674,9 @@ async def watchdog():
if interface2_enabled:
if interface2 is not None and not retry_int2:
try:
with suppress_stdout():
with contextlib.redirect_stdout(None):
interface2.localNode.getMetadata()
print(f"System: if you see this upgrade python to >3.4")
except Exception as e:
logger.error(f"System: communicating with interface2, trying to reconnect: {e}")
retry_int2 = True
@@ -668,7 +691,7 @@ async def watchdog():
enemySpotted2 += ", " + get_name_from_number(closest_nodes2[0]['id'], 'short', 2)
enemySpotted2 += ", " + str(closest_nodes2[0]['id'])
enemySpotted2 += ", " + decimal_to_hex(closest_nodes2[0]['id'])
enemySpotted += f" at {closest_nodes1[0]['distance']}m"
enemySpotted2 += f" at {closest_nodes2[0]['distance']}m"
except Exception as e:
pass

View File

@@ -33,7 +33,7 @@ def auto_response(message, snr, rssi, hop, message_from_id, channel_number, devi
if len(cmds) > 0:
# sort the commands by index value
cmds = sorted(cmds, key=lambda k: k['index'])
logger.debug(f"System: Bot Detected: {cmds}")
logger.debug(f"System: Bot detected Commands:{cmds}")
# run the first command after sorting
bot_response = command_handler[cmds[0]['cmd']]()
@@ -85,6 +85,25 @@ def handle_testing(hop, snr, rssi):
else:
return "🏓Testing 1,2,3 " + hop
def onDisconnect(interface):
global retry_int1, retry_int2
rxType = type(interface).__name__
if rxType == 'SerialInterface':
rxInterface = interface.__dict__.get('devPath', 'unknown')
logger.critical(f"System: Lost Connection to Device {rxInterface}")
if port1 in rxInterface:
retry_int1 = True
elif interface2_enabled and port2 in rxInterface:
retry_int2 = True
if rxType == 'TCPInterface':
rxHost = interface.__dict__.get('hostname', 'unknown')
logger.critical(f"System: Lost Connection to Device {rxHost}")
if hostname1 in rxHost and interface1_type == 'tcp':
retry_int1 = True
elif interface2_enabled and hostname2 in rxHost and interface2_type == 'tcp':
retry_int2 = True
def onReceive(packet, interface):
# extract interface defailts from interface object
rxType = type(interface).__name__
@@ -198,7 +217,7 @@ def onReceive(packet, interface):
send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, 0, rxNode)
else:
# message is not for bot to respond to
# ignore the message but add it to the message history and repeat it if enabled
# ignore the message but add it to the message history list
if zuluTime:
timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S")
else:
@@ -209,14 +228,19 @@ def onReceive(packet, interface):
else:
msg_history.pop(0)
msg_history.append((get_name_from_number(message_from_id, 'long', rxNode), message_string, channel_number, timestamp, rxNode))
# check if repeater is enabled and the other interface is enabled
# print the message to the log and sdout
logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Ignoring Message:" + CustomFormatter.white +\
f" {message_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}")
if log_messages_to_file:
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
# repeat the message on the other device
if repeater_enabled and interface2_enabled:
# repeat the message on the other device
# wait a 700ms to avoid message collision from lora-ack.
time.sleep(0.7)
rMsg = (f"{message_string} From:{get_name_from_number(message_from_id, 'short', rxNode)}")
# if channel found in the repeater list repeat the message
# wait a 700ms to avoid message collision from lora-ack
time.sleep(0.7)
if str(channel_number) in repeater_channels:
if rxNode == 1:
logger.debug(f"Repeating message on Device2 Channel:{channel_number}")
@@ -224,11 +248,6 @@ def onReceive(packet, interface):
elif rxNode == 2:
logger.debug(f"Repeating message on Device1 Channel:{channel_number}")
send_message(rMsg, channel_number, 0, 1)
else:
# nothing to do for us
logger.info(f"Ignoring Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Message:" + CustomFormatter.white +\
f" {message_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}")
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | {message_string}")
except KeyError as e:
logger.critical(f"System: Error processing packet: {e} Device:{rxNode}")
print(packet) # print the packet for debugging
@@ -238,6 +257,7 @@ async def start_rx():
print (CustomFormatter.bold_white + f"\nMeshtastic Autoresponder Bot CTL+C to exit\n" + CustomFormatter.reset)
# Start the receive subscriber using pubsub via meshtastic library
pub.subscribe(onReceive, 'meshtastic.receive')
pub.subscribe(onDisconnect, 'meshtastic.connection.lost')
logger.info(f"System: Autoresponder Started for Device1 {get_name_from_number(myNodeNum1, 'long', 1)},"
f"{get_name_from_number(myNodeNum1, 'short', 1)}. NodeID: {myNodeNum1}, {decimal_to_hex(myNodeNum1)}")
if interface2_enabled:

View File

@@ -10,4 +10,5 @@ dadjokes
openmeteo_requests
retry_requests
numpy
geopy
geopy
schedule