mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
Compare commits
27 Commits
v1.0.0-alp
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ad123dc93c | ||
|
|
22983133ee | ||
|
|
60c4a885fd | ||
|
|
95d6d7b7d5 | ||
|
|
37a86b7e2b | ||
|
|
c4ef1251c9 | ||
|
|
9d7e42aa60 | ||
|
|
8536e354ad | ||
|
|
e3faf676cd | ||
|
|
630e016805 | ||
|
|
23b8b8135c | ||
|
|
7f0b4c079a | ||
|
|
47649cdedc | ||
|
|
7915798ca2 | ||
|
|
86cd88910a | ||
|
|
229ccc75f0 | ||
|
|
6f3e3a7957 | ||
|
|
1f1996b909 | ||
|
|
c2069da919 | ||
|
|
458957ddfb | ||
|
|
95c266fbf3 | ||
|
|
4857940165 | ||
|
|
4c780d09e7 | ||
|
|
d616867cd1 | ||
|
|
909c4ad3bc | ||
|
|
44eff643a9 | ||
|
|
a223e57690 |
13
README.md
13
README.md
@@ -141,6 +141,18 @@ 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
|
||||
Python 3.4 and likely higher is needed, developed on latest release.
|
||||
|
||||
@@ -160,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
|
||||
```
|
||||
|
||||
49
mesh_bot.py
49
mesh_bot.py
@@ -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,
|
||||
@@ -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):
|
||||
@@ -196,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__
|
||||
@@ -364,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:
|
||||
@@ -396,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:
|
||||
|
||||
@@ -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, try sending location 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 = ""
|
||||
|
||||
@@ -60,13 +60,13 @@ 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 for system logs
|
||||
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
|
||||
file_handler.setLevel(logging.INFO) # INFO used for messages to disk
|
||||
file_handler.setFormatter(logging.Formatter(msgLogFormat))
|
||||
msgLogger.addHandler(file_handler)
|
||||
|
||||
@@ -24,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()
|
||||
|
||||
@@ -64,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
|
||||
@@ -204,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():
|
||||
@@ -230,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)
|
||||
@@ -244,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:
|
||||
@@ -322,6 +330,7 @@ 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 = []
|
||||
@@ -336,7 +345,7 @@ def get_closest_nodes(nodeInt=1,returnCount=3):
|
||||
longitude = node['position']['longitude']
|
||||
|
||||
#lastheard time in unix time
|
||||
lastheard = node['lastHeard']
|
||||
lastheard = node.get('lastHeard', 0)
|
||||
#if last heard is over 24 hours ago, ignore the node
|
||||
if lastheard < (time.time() - 86400):
|
||||
continue
|
||||
@@ -377,7 +386,7 @@ def get_closest_nodes(nodeInt=1,returnCount=3):
|
||||
longitude = node['position']['longitude']
|
||||
|
||||
#lastheard time in unix time
|
||||
lastheard = node['lastHeard']
|
||||
lastheard = node.get('lastHeard', 0)
|
||||
#if last heard is over 24 hours ago, ignore the node
|
||||
if lastheard < (time.time() - 86400):
|
||||
continue
|
||||
@@ -415,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 = ''
|
||||
@@ -426,6 +435,7 @@ 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:
|
||||
@@ -506,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
|
||||
|
||||
20
pong_bot.py
20
pong_bot.py
@@ -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__
|
||||
@@ -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:
|
||||
|
||||
@@ -10,4 +10,5 @@ dadjokes
|
||||
openmeteo_requests
|
||||
retry_requests
|
||||
numpy
|
||||
geopy
|
||||
geopy
|
||||
schedule
|
||||
|
||||
Reference in New Issue
Block a user