Compare commits

...

25 Commits
RC9 ... RC10

Author SHA1 Message Date
SpudGunMan
fe1854f2d8 Update system.py 2024-08-13 15:22:38 -07:00
SpudGunMan
df9a34dc16 Update system.py 2024-08-13 15:02:40 -07:00
SpudGunMan
e762ea4b90 Update install.sh 2024-08-13 14:23:49 -07:00
SpudGunMan
3b725837ac fixes 2024-08-13 13:57:12 -07:00
SpudGunMan
23efd8e5d8 Update mesh_bot.py 2024-08-13 13:50:53 -07:00
SpudGunMan
b61463f570 Update mesh_bot.py 2024-08-13 13:40:08 -07:00
SpudGunMan
8339233459 Update install.sh 2024-08-13 13:35:30 -07:00
SpudGunMan
df68111f0c Update config.template 2024-08-13 13:34:33 -07:00
SpudGunMan
b73ad38156 Update install.sh
reference https://github.com/SpudGunMan/meshing-around/issues/37
2024-08-13 00:14:50 -07:00
SpudGunMan
2b7d1ed09f Update README.md 2024-08-13 00:00:16 -07:00
SpudGunMan
f1ef5fa787 cleanup 2024-08-12 23:49:50 -07:00
Kelly
ec14e07513 Merge pull request #39 from SpudGunMan/case_test
refactor autoresponse logic
2024-08-12 11:52:50 -07:00
SpudGunMan
efdd5fab66 enhance 2024-08-12 11:40:55 -07:00
SpudGunMan
4fa114a3f2 fix 2024-08-12 03:11:10 -07:00
SpudGunMan
ab64ff14b1 Update mesh_bot.py 2024-08-12 02:59:27 -07:00
SpudGunMan
65609c5822 Update mesh_bot.py 2024-08-12 02:57:34 -07:00
SpudGunMan
bdd41c0434 Update mesh_bot.py 2024-08-12 02:54:32 -07:00
SpudGunMan
80da793c8d Update mesh_bot.py 2024-08-12 02:53:24 -07:00
SpudGunMan
ba6c296b14 Update mesh_bot.py 2024-08-12 02:52:38 -07:00
SpudGunMan
9ae95752ad Update mesh_bot.py 2024-08-12 02:51:34 -07:00
SpudGunMan
9ba430c53c enhance 2024-08-12 02:36:53 -07:00
SpudGunMan
9e605a2717 Update settings.py 2024-08-12 01:23:52 -07:00
SpudGunMan
aeab22010f typo 2024-08-12 00:54:19 -07:00
SpudGunMan
2d20f4479c fixMOTD and settings 2024-08-12 00:43:10 -07:00
SpudGunMan
6546679def rearrange auto if 2024-08-11 23:47:52 -07:00
7 changed files with 376 additions and 247 deletions

View File

@@ -1,24 +1,26 @@
# meshing-around
Random Mesh Scripts for Network Testing and BBS Activities for Use with Meshtastic Nodes
Random Mesh Scripts for Network Testing and BBS Activities for Use with [Meshtastic](https://meshtastic.org/docs/introduction/) Nodes
![alt text](etc/pong-bot.jpg "Example Use")
## mesh_bot.sh
The feature-rich bot requires the internet for full functionality. These responder bots will trap keywords like ping and respond to a DM (direct message) with pong! The script will also monitor the group channels for keywords to trap. You can also `Ping @Data to Echo` as an example for further processing.
The feature-rich bot requires the internet for full functionality. These responder bots will trap keywords like ping and respond to a DM (direct message) with pong! The script will also monitor the group channels for keywords to trap. You can also `Ping @Data to Echo` as an example.
Along with network testing, this bot has a lot of other features, like simple mail messaging you can leave for another device, and when that device is seen, it can send the mail as a DM.
Along with network testing, this bot has a lot of other fun features, like simple mail messaging you can leave for another device, and when that device is seen, it can send the mail as a DM.
The bot is also capable of using dual radio/nodes, so you can monitor two networks at the same time and send messages to nodes using the same `bbspost @nodeNumber #message` or `bbspost @nodeShportName #message` function. There is a small message board to fit in the constraints of Meshtastic for posting bulletin messages with `bbspost $subject #message`.
The bot will report on anyone who is getting close to the device if in a remote location.
The bot will report on anyone who is getting close to the configured lat/long, if in a remote location.
Store and forward-like message re-play with `messages`, and there is a repeater module for dual radio bots to cross post messages. Messages are also logged locally to disk.
The bot can also be used to monitor a frequency and let you know when activity is seen. Using Hamlib to watch the S meter on a connected radio. You can send alerts to channels when a frequency is detected for 20 seconds within the thresholds set in config.ini
The bot can also be used to monitor a radio frequency and let you know when high SNR RF activity is seen. Using Hamlib(rigctld) to watch the S meter on a connected radio. You can send alerts to channels when a frequency is detected for 20 seconds within the thresholds set in config.ini
Any messages that are over 160 characters are chunked into 160 message bytes to help traverse hops, in testing, this keeps delivery success higher.
- Various solar details for radio propagation
Full list of commands for the bot.
- Various solar details for radio propagation (spaceWeather module)
- `sun` and `moon` return info on rise and set local time
- `solar` gives an idea of the x-ray flux
- `hfcond` returns a table of HF solar conditions
@@ -43,14 +45,14 @@ Any messages that are over 160 characters are chunked into 160 message bytes to
Stripped-down bot, mostly around for archive purposes. The mesh-bot enhanced modules can be disabled by config to disable features.
## Hardware
The project is written on Linux on a Pi and should work anywhere meshtastic Python modules will function, with any supported meshtastic hardware. While BLE and TCP will work, they are not as reliable as serial connections.
- Firmware 2.3.14/15 could also have an issue with connectivity with slower devices.
The project is written on Linux on a Pi and should work anywhere [Meshtastic](https://meshtastic.org/docs/software/python/cli/) Python modules will function, with any supported [Meshtastic](https://meshtastic.org/docs/getting-started/) hardware. While BLE and TCP will work, they are not as reliable as serial connections.
## Install
Clone the project with `git clone https://github.com/spudgunman/meshing-around`
code is under a lot of development, so check back often with `git pull`
Copy [config.template](config.template) to `config.ini` and edit for your needs.
- Optionally
Optionally:
- `install.sh` will automate optional venv and requirements installation.
- `launch.sh` will activate and launch the app in the venv if built.
@@ -104,7 +106,9 @@ Sentry Bot detects anyone comeing close to the bot-node
```
# detect anyone close to the bot
SentryEnabled = True
# holdoff time multiplied by minutes(20) of the watchdog
# radius in meters to detect someone close to the bot
SentryRadius = 100
# holdoff time multiplied by seconds(20) of the watchdog
SentryChannel = 9
# channel to send a message to when the watchdog is triggered
SentryHoldoff = 2
@@ -167,7 +171,7 @@ To enable emoji in the Debian console, install the fonts `sudo apt-get install f
# Recognition
I used ideas and snippets from other responder bots and want to call them out!
- https://github.com/Murturtle/MeshLink
- https://github.com/pdxlocations/Meshtastic-Python-Examples
- https://github.com/pdxlocations/meshtastic-Python-Examples
- https://github.com/geoffwhittington/meshtastic-matrix-relay
GitHub user PiDiBi looking at test functions and other suggestions like wxc, CPU use, and alerting ideas

View File

@@ -32,23 +32,28 @@ motd = Thanks for using MeshBOT! Have a good day!
welcome_message = MeshBot, here for you like a friend who is not. Try sending: ping @foo or, cmd
# enable or disable the Joke module
DadJokes = True
# enable or disable the Solar module
spaceWeather = True
# StoreForward Enabled and Limits
StoreForward = True
StoreLimit = 3
# 24 hour clock
zuluTime = True
zuluTime = False
# wait time for URL requests
URL_TIMEOUT = 10
urlTimeout = 10
# logging to file of the non Bot messages
LogMessagesToFile = False
[sentry]
# detect anyone close to the bot
SentryEnabled = True
# radius in meters to detect someone close to the bot
SentryRadius = 100
# holdoff time multiplied by minutes(20) of the watchdog
SentryChannel = 9
# channel to send a message to when the watchdog is triggered
SentryHoldoff = 2
SentryChannel = 9
# holdoff time multiplied by seconds(20) of the watchdog
SentryHoldoff = 9
# list of ignored nodes numbers ex: 2813308004,4258675309
sentryIgnoreList =
@@ -73,10 +78,6 @@ UseMeteoWxAPI = False
# Default to metric units rather than imperial
useMetric = False
# solar module
[solar]
enabled = True
# repeater module
[repeater]
enabled = False

View File

@@ -7,26 +7,47 @@ cd "$(dirname "$0")"
sudo usermod -a -G dialout $USER
sudo usermod -a -G tty $USER
# generate config file
# generate config file, check if it exists
if [ -f config.ini ]; then
printf "\nConfig file already exists, moving to backup config.old\n"
mv config.ini config.old
fi
cp config.template config.ini
printf "\nConfig file generated\n"
# set virtual environment and install dependencies
printf "\nMeshing Around Installer\n"
#check if python3 has venv module
if ! python3 -m venv --help &> /dev/null
then
printf "Python3 venv module not found, please install python3-venv with your OS\n"
else
printf "Python3 venv module found\n"
fi
echo "Do you want to install the bot in a virtual environment? (y/n)"
read venv
if [ $venv == "y" ]; then
# set virtual environment
echo "Creating virtual environment..."
python3 -m venv venv
source venv/bin/activate
if ! python3 -m venv --help &> /dev/null
then
printf "Python3 venv module not found, please install python3-venv with your OS\n"
exit 1
else
echo "Creating virtual environment..."
python3 -m venv venv
source venv/bin/activate
# install dependencies
pip install -U -r requirements.txt
else
printf "\nSkipping virtual environment...\n"
# install dependencies
echo "Are you on Raspberry Pi? should we add --break-system-packages to the pip install command? (y/n)"
printf "Are you on Raspberry Pi?\nshould we add --break-system-packages to the pip install command? (y/n)"
read rpi
if [ $rpi == "y" ]; then
pip install -U -r requirements.txt --break-system-packages
@@ -36,7 +57,7 @@ else
fi
printf "\n\n"
echo "Which bot do you want to install as a service? (pong/mesh/n)"
echo "Which bot do you want to install as a service? Pong Mesh or None? (pong/mesh/n)"
read bot
#set the correct path in the service file

View File

@@ -10,169 +10,194 @@ from modules.system import *
def auto_response(message, snr, rssi, hop, message_from_id, channel_number, deviceID):
#Auto response to messages
bot_response = ""
if "ping" in message.lower():
#Check if the user added @foo to the message
if "@" in message:
if hop == "Direct":
bot_response = "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}" + " and copy: " + message.split("@")[1]
else:
bot_response = "🏓PONG, " + hop + " and copy: " + message.split("@")[1]
else:
if hop == "Direct":
bot_response = "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}"
else:
bot_response = "🏓PONG, " + hop
elif "pong" in message.lower():
bot_response = "🏓PING!!"
elif "motd" in message.lower():
#check if the user wants to set the motd by using $
if "$" in message:
motd = message.split("$")[1]
global MOTD
MOTD = motd
bot_response = "MOTD Set to: " + MOTD
else:
bot_response = MOTD
elif "messages" in message.lower():
response = ""
for msgH in msg_history:
# check if the message is from the same interface
if msgH[4] == deviceID:
# check if the message is from the same channel
if msgH[2] == channel_number or msgH[2] == publicChannel:
# consider message safe to send
response += f"\n{msgH[0]}: {msgH[1]}"
if len(response) > 0:
bot_response = "Message History:" + response
else:
bot_response = "No messages in history"
elif "bbshelp" in message.lower():
bot_response = bbs_help()
elif "cmd" in message.lower() or "cmd?" in message.lower():
bot_response = help_message
elif "sun" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
bot_response = get_sun(str(location[0]),str(location[1]))
elif "hfcond" in message.lower():
bot_response = hf_band_conditions()
elif "solar" in message.lower():
bot_response = drap_xray_conditions() + "\n" + solar_conditions()
elif "lheard" in message.lower() or "sitrep" in message.lower():
bot_response = "Last heard:\n" + str(get_node_list(1))
chutil1 = interface1.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil1 = "{:.2f}".format(chutil1)
if interface2_enabled:
bot_response += "Port2:\n" + str(get_node_list(2))
chutil2 = interface2.nodes.get(decimal_to_hex(myNodeNum2), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil2 = "{:.2f}".format(chutil2)
bot_response += "Ch Use: " + str(chutil1) + "%"
if interface2_enabled:
bot_response += " P2:" + str(chutil2) + "%"
elif "whereami" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
where = where_am_i(str(location[0]),str(location[1]))
bot_response = where
elif "tide" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
tide = get_tide(str(location[0]),str(location[1]))
bot_response = tide
elif "moon" in message.lower():
location = get_node_location(message_from_id, deviceID, channel_number)
moon = get_moon(str(location[0]),str(location[1]))
bot_response = moon
elif "wxalert" in message.lower() or "wxa" in message.lower():
if use_meteo_wxApi:
bot_response = "wxalert is not supported"
else:
location = get_node_location(message_from_id, deviceID)
weatherAlert = getActiveWeatherAlertsDetail(str(location[0]),str(location[1]))
bot_response = weatherAlert
elif "wxc" in message.lower() or "wx" in message.lower():
location = get_node_location(message_from_id, deviceID)
if use_meteo_wxApi and not "wxc" in message.lower() and not use_metric:
logger.debug(f"System: Bot Returning Open-Meteo API for weather imperial")
weather = get_wx_meteo(str(location[0]),str(location[1]))
elif use_meteo_wxApi:
logger.debug(f"System: Bot Returning Open-Meteo API for weather metric")
weather = get_wx_meteo(str(location[0]),str(location[1]),1)
elif not use_meteo_wxApi and "wxc" in message.lower() or use_metric:
logger.debug(f"System: Bot Returning NOAA API for weather metric")
weather = get_weather(str(location[0]),str(location[1]),1)
else:
logger.debug(f"System: Bot Returning NOAA API for weather imperial")
weather = get_weather(str(location[0]),str(location[1]))
bot_response = weather
elif "joke" in message.lower():
bot_response = tell_joke()
elif "bbslist" in message.lower():
bot_response = bbs_list_messages()
elif "bbspost" in message.lower():
# Check if the user added a subject to the message
if "$" in message and not "example:" in message:
subject = message.split("$")[1].split("#")[0]
subject = subject.rstrip()
if "#" in message:
body = message.split("#")[1]
body = body.rstrip()
logger.info(f"System: BBS Post: {subject} Body: {body}")
bot_response = bbs_post_message(subject,body,message_from_id)
elif not "example:" in message:
bot_response = "example: bbspost $subject #message"
# Check if the user added a node number to the message
elif "@" in message and not "example:" in message:
toNode = message.split("@")[1].split("#")[0]
toNode = toNode.rstrip()
# if toNode is a string look for short name and convert to number
if toNode.isalpha() or not toNode.isnumeric():
toNode = get_num_from_short_name(toNode, deviceID)
if toNode == 0:
bot_response = "Node not found " + message.split("@")[1].split("#")[0]
return bot_response
else:
logger.debug(f"System: bbspost, name lookup found: {toNode}")
if "#" in message:
body = message.split("#")[1]
bot_response = bbs_post_dm(toNode, body, message_from_id)
else:
bot_response = "example: bbspost @nodeNumber/ShortName #message"
elif not "example:" in message:
bot_response = "example: bbspost $subject #message, or bbspost @node #message"
message_lower = message.lower()
bot_response = "I'm sorry, I'm afraid I can't do that."
command_handler = {
"ping": lambda: handle_ping(message, hop, snr, rssi),
"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),
"wxc": lambda: handle_wxc(message_from_id, deviceID, 'wxc'),
"wx": lambda: handle_wxc(message_from_id, deviceID, 'wx'),
"joke": tell_joke,
"bbslist": bbs_list_messages,
"bbspost": lambda: handle_bbspost(message, message_from_id, deviceID),
"bbsread": lambda: handle_bbsread(message),
"bbsdelete": lambda: handle_bbsdelete(message, message_from_id),
"messages": lambda: handle_messages(deviceID, channel_number, msg_history, publicChannel),
"cmd": lambda: help_message,
"cmd?": lambda: help_message,
"sun": lambda: handle_sun(message_from_id, deviceID, channel_number),
"hfcond": hf_band_conditions,
"solar": lambda: drap_xray_conditions() + "\n" + solar_conditions(),
"lheard": lambda: handle_lheard(),
"sitrep": lambda: handle_lheard(),
"whereami": lambda: handle_whereami(message_from_id, deviceID, channel_number),
"tide": lambda: handle_tide(message_from_id, deviceID, channel_number),
"moon": lambda: handle_moon(message_from_id, deviceID, channel_number),
"ack": lambda: handle_ack(hop, snr, rssi),
"testing": lambda: handle_testing(hop, snr, rssi),
"test": lambda: handle_testing(hop, snr, rssi),
}
cmds = [] # list to hold the commands found in the message
for key in command_handler:
if key in message_lower.split(' '):
cmds.append({'cmd': key, 'index': message_lower.index(key)})
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}")
# run the first command after sorting
bot_response = command_handler[cmds[0]['cmd']]()
elif "bbsread" in message.lower():
# Check if the user added a message number to the message
if "#" in message and not "example:" in message:
messageID = int(message.split("#")[1])
bot_response = bbs_read_message(messageID)
elif not "example:" in message:
bot_response = "Please add a message number example: bbsread #14"
elif "bbsdelete" in message.lower():
# Check if the user added a message number to the message
if "#" in message and not "example:" in message:
messageID = int(message.split("#")[1])
bot_response = bbs_delete_message(messageID, message_from_id)
elif not "example:" in message:
bot_response = "Please add a message number example: bbsdelete #14"
elif "ack" in message.lower():
if hop == "Direct":
bot_response = "🏓ACK-ACK! " + f"SNR:{snr} RSSI:{rssi}"
else:
bot_response = "🏓ACK-ACK! " + hop
elif "testing" in message.lower() or "test" in message.lower():
if hop == "Direct":
bot_response = "🏓Testing 1,2,3 " + f"SNR:{snr} RSSI:{rssi}"
else:
bot_response = "🏓Testing 1,2,3 " + hop
else:
bot_response = "I'm sorry, I'm afraid I can't do that."
# wait a 700ms to avoid message collision from lora-ack
time.sleep(0.7)
return bot_response
def handle_ping(message, hop, snr, rssi):
if "@" in message:
if hop == "Direct":
return "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}" + " and copy: " + message.split("@")[1]
else:
return "🏓PONG, " + hop + " and copy: " + message.split("@")[1]
else:
if hop == "Direct":
return "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}"
else:
return "🏓PONG, " + hop
def handle_motd(message):
global MOTD
if "$" in message:
motd = message.split("$")[1]
MOTD = motd.rstrip()
return "MOTD Set to: " + MOTD
else:
return MOTD
def handle_wxalert(message_from_id, deviceID):
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]))
return weatherAlert
def handle_wxc(message_from_id, deviceID, cmd):
location = get_node_location(message_from_id, deviceID)
if use_meteo_wxApi and not "wxc" in cmd and not use_metric:
logger.debug(f"System: Bot Returning Open-Meteo API for weather imperial")
weather = get_wx_meteo(str(location[0]), str(location[1]))
elif use_meteo_wxApi:
logger.debug(f"System: Bot Returning Open-Meteo API for weather metric")
weather = get_wx_meteo(str(location[0]), str(location[1]), 1)
elif not use_meteo_wxApi and "wxc" in cmd or use_metric:
logger.debug(f"System: Bot Returning NOAA API for weather metric")
weather = get_weather(str(location[0]), str(location[1]), 1)
else:
logger.debug(f"System: Bot Returning NOAA API for weather imperial")
weather = get_weather(str(location[0]), str(location[1]))
return weather
def handle_bbspost(message, message_from_id, deviceID):
if "$" in message and not "example:" in message:
subject = message.split("$")[1].split("#")[0]
subject = subject.rstrip()
if "#" in message:
body = message.split("#")[1]
body = body.rstrip()
logger.info(f"System: BBS Post: {subject} Body: {body}")
return bbs_post_message(subject, body, message_from_id)
elif not "example:" in message:
return "example: bbspost $subject #message"
elif "@" in message and not "example:" in message:
toNode = message.split("@")[1].split("#")[0]
toNode = toNode.rstrip()
if toNode.isalpha() or not toNode.isnumeric():
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)
else:
return "example: bbspost @nodeNumber/ShortName #message"
elif not "example:" in message:
return "example: bbspost $subject #message, or bbspost @node #message"
def handle_bbsread(message):
if "#" in message and not "example:" in message:
messageID = int(message.split("#")[1])
return bbs_read_message(messageID)
elif not "example:" in message:
return "Please add a message number example: bbsread #14"
def handle_bbsdelete(message, message_from_id):
if "#" in message and not "example:" in message:
messageID = int(message.split("#")[1])
return bbs_delete_message(messageID, message_from_id)
elif not "example:" in message:
return "Please add a message number example: bbsdelete #14"
def handle_messages(deviceID, channel_number, msg_history, publicChannel):
response = ""
for msgH in msg_history:
if msgH[4] == deviceID:
if msgH[2] == channel_number or msgH[2] == publicChannel:
response += f"\n{msgH[0]}: {msgH[1]}"
if len(response) > 0:
return "Message History:" + response
else:
return "No messages in history"
def handle_sun(message_from_id, deviceID, channel_number):
location = get_node_location(message_from_id, deviceID, channel_number)
return get_sun(str(location[0]), str(location[1]))
def handle_lheard():
bot_response = "Last heard:\n" + str(get_node_list(1))
chutil1 = interface1.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil1 = "{:.2f}".format(chutil1)
if interface2_enabled:
bot_response += "Port2:\n" + str(get_node_list(2))
chutil2 = interface2.nodes.get(decimal_to_hex(myNodeNum2), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil2 = "{:.2f}".format(chutil2)
bot_response += "Ch Use: " + str(chutil1) + "%"
if interface2_enabled:
bot_response += " P2:" + str(chutil2) + "%"
return bot_response
def handle_whereami(message_from_id, deviceID, channel_number):
location = get_node_location(message_from_id, deviceID, channel_number)
return where_am_i(str(location[0]), str(location[1]))
def handle_tide(message_from_id, deviceID, channel_number):
location = get_node_location(message_from_id, deviceID, channel_number)
return get_tide(str(location[0]), str(location[1]))
def handle_moon(message_from_id, deviceID, channel_number):
location = get_node_location(message_from_id, deviceID, channel_number)
return get_moon(str(location[0]), str(location[1]))
def handle_ack(hop, snr, rssi):
if hop == "Direct":
return "🏓ACK-ACK! " + f"SNR:{snr} RSSI:{rssi}"
else:
return "🏓ACK-ACK! " + hop
def handle_testing(hop, snr, rssi):
if hop == "Direct":
return "🏓Testing 1,2,3 " + f"SNR:{snr} RSSI:{rssi}"
else:
return "🏓Testing 1,2,3 " + hop
def onReceive(packet, interface):
# extract interface defailts from interface object
rxType = type(interface).__name__
@@ -360,8 +385,10 @@ async def start_rx():
logger.debug(f"System: Location Telemetry Enabled using NOAA API")
if dad_jokes_enabled:
logger.debug(f"System: Dad Jokes Enabled!")
if motd_enabled:
logger.debug(f"System: MOTD Enabled using {MOTD}")
if sentry_enabled:
logger.debug(f"System: Sentry Mode Enabled")
logger.debug(f"System: Sentry Mode Enabled {sentry_radius}m radius reporting to channel:{secure_channel}")
if store_forward_enabled:
logger.debug(f"System: Store and Forward Enabled using limit: {storeFlimit}")
if useDMForResponse:

View File

@@ -40,6 +40,26 @@ if config.sections() == []:
config.write(open(config_file, 'w'))
print (f"System: Config file created, check {config_file} or review the config.template")
if 'sentry' not in config:
config['Sentry'] = {'SentryEnabled': 'False', 'SentryChannel': '2', 'SentryHoldoff': '9', 'sentryIgnoreList': '', 'SentryRadius': '100'}
config.write(open(config_file, 'w'))
if 'location' not in config:
config['location'] = {'enabled': 'True', 'lat': '48.50', 'lon': '-123.0', 'UseMeteoWxAPI': 'False', 'useMetric': 'False', 'NOAAforecastDuration': '4', 'NOAAalertCount': '2', 'NOAAalertsEnabled': 'True'}
config.write(open(config_file, 'w'))
if 'bbs' not in config:
config['bbs'] = {'enabled': 'False', 'bbsdb': 'bbsdb.pkl', 'bbs_ban_list': '', 'bbs_admin_list': ''}
config.write(open(config_file, 'w'))
if 'repeater' not in config:
config['repeater'] = {'enabled': 'False', 'repeater_channels': ''}
config.write(open(config_file, 'w'))
if 'radioMon' not in config:
config['radioMon'] = {'enabled': 'False', 'rigControlServerAddress': 'localhost:4532', 'sigWatchBrodcastCh': '2', 'signalDetectionThreshold': '-10', 'signalHoldTime': '10', 'signalCooldown': '5', 'signalCycleLimit': '5'}
config.write(open(config_file, 'w'))
# interface1 settings
interface1_type = config['interface'].get('type', 'serial')
port1 = config['interface'].get('port', '')
@@ -58,36 +78,42 @@ else:
# variables
try:
storeFlimit = config['general'].getint('StoreLimit', 3) # default 3 messages for S&F
useDMForResponse = config['general'].getboolean('respond_by_dm_only', True)
publicChannel = config['general'].getint('defaultChannel', 0) # the meshtastic public channel
location_enabled = config['location'].getboolean('enabled', False)
zuluTime = config['general'].getboolean('zuluTime', False) # aka 24 hour time
log_messages_to_file = config['general'].getboolean('LogMessagesToFile', True) # 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
welcome_message = config['general'].get(f'welcome_message', WELCOME_MSG)
welcome_message = (f"{welcome_message}").replace('\\n', '\n') # allow for newlines in the welcome message
motd_enabled = config['general'].getboolean('motdEnabled', True)
dad_jokes_enabled = config['general'].getboolean('DadJokes', True)
solar_conditions_enabled = config['general'].getboolean('spaceWeather', True)
sentry_enabled = config['sentry'].getboolean('SentryEnabled', False) # default False
secure_channel = config['sentry'].getint('SentryChannel', 2) # default 2
sentry_holdoff = config['sentry'].getint('SentryHoldoff', 9) # default 9
sentryIgnoreList = config['sentry'].get('sentryIgnoreList', '').split(',')
sentry_radius = config['sentry'].getint('SentryRadius', 100) # default 100 meters
location_enabled = config['location'].getboolean('enabled', True)
latitudeValue = config['location'].getfloat('lat', 48.50)
longitudeValue = config['location'].getfloat('lon', -123.0)
use_meteo_wxApi = config['location'].getboolean('UseMeteoWxAPI', False) # default False use NOAA
use_metric = config['location'].getboolean('useMetric', False) # default Imperial units
zuluTime = config['general'].getboolean('zuluTime', False)
welcome_message = config['general'].get(f'welcome_message', WELCOME_MSG)
welcome_message = (f"{welcome_message}").replace('\\n', '\n') # allow for newlines in the welcome message
solar_conditions_enabled = config['solar'].getboolean('enabled', False)
forecastDuration = config['location'].getint('NOAAforecastDuration', 4) # NOAA forcast days
numWxAlerts = config['location'].getint('NOAAalertCount', 2) # default 2 alerts
wxAlertsEnabled = config['location'].getboolean('NOAAalertsEnabled', True) # default True not enabled yet
bbs_enabled = config['bbs'].getboolean('enabled', False)
bbsdb = config['bbs'].get('bbsdb', 'bbsdb.pkl')
dad_jokes_enabled = config['general'].getboolean('DadJokes', False)
store_forward_enabled = config['general'].getboolean('StoreForward', False)
log_messages_to_file = config['general'].getboolean('LogMessagesToFile', True) # default True
sentry_enabled = config['general'].getboolean('SentryEnabled', True) # default True
secure_channel = config['general'].getint('SentryChannel', 2) # default 2
sentry_holdoff = config['general'].getint('SentryHoldoff', 9) # default 9
sentryIgnoreList = config['general'].get('sentryIgnoreList', '').split(',')
sentry_radius = config['general'].getint('SentryRadius', 100) # default 100 meters
config['general'].get('motd', MOTD)
urlTimeoutSeconds = config['general'].getint('URL_TIMEOUT', 10) # default 10 seconds
forecastDuration = config['general'].getint('NOAAforecastDuration', 4) # NOAA forcast days
numWxAlerts = config['general'].getint('NOAAalertCount', 2) # default 2 alerts
bbs_ban_list = config['bbs'].get('bbs_ban_list', '').split(',')
bbs_admin_list = config['bbs'].get('bbs_admin_list', '').split(',')
repeater_enabled = config['repeater'].getboolean('enabled', False)
repeater_channels = config['repeater'].get('repeater_channels', '').split(',')
radio_dectection_enabled = config['radioMon'].getboolean('enabled', False)
rigControlServerAddress = config['radioMon'].get('rigControlServerAddress', 'localhost:4532') # default localhost:4532
sigWatchBrodcastCh = config['radioMon'].get('sigWatchBrodcastCh', '2').split(',') # default Channel 2

View File

@@ -20,11 +20,18 @@ if ping_enabled:
trap_list = trap_list + trap_list_ping
help_message = help_message + "ping"
# Sitrep Configuration
if sitrep_enabled:
trap_list_sitrep = ("sitrep", "lheard")
trap_list = trap_list + trap_list_sitrep
help_message = help_message + ", sitrep"
# MOTD Configuration
if motd_enabled:
trap_list_motd = ("motd",)
trap_list = trap_list + trap_list_motd
help_message = help_message + ", motd"
# Solar Conditions Configuration
if solar_conditions_enabled:
from modules.solarconditions import * # from the spudgunman/meshing-around repo
@@ -161,10 +168,23 @@ def get_num_from_short_name(short_name, nodeInt=1):
for node in interface1.nodes.values():
if 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():
return node['num']
if nodeInt == 2:
for node in interface2.nodes.values():
if 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():
return node['num']
return 0
def get_node_list(nodeInt=1):
@@ -615,6 +635,9 @@ async def watchdog():
if sentry_loop >= sentry_holdoff and lastSpotted != enemySpotted:
logger.warning(f"System: {enemySpotted} is close to your location on Interface1")
send_message(f"Sentry1: {enemySpotted}", secure_channel, 0, 1)
if interface2_enabled:
await asyncio.sleep(1.5)
send_message(f"Sentry1: {enemySpotted}", secure_channel, 0, 2)
sentry_loop = 0
lastSpotted = enemySpotted
else:
@@ -651,6 +674,9 @@ async def watchdog():
if sentry_loop2 >= sentry_holdoff and lastSpotted2 != enemySpotted2:
logger.warning(f"System: {enemySpotted2} is close to your location on Interface2")
# send to secure channel on both interfaces
send_message(f"Sentry2: {enemySpotted2}", secure_channel, 0, 1)
await asyncio.sleep(1.5)
send_message(f"Sentry2: {enemySpotted2}", secure_channel, 0, 2)
sentry_loop2 = 0
lastSpotted2 = enemySpotted2

View File

@@ -10,57 +10,81 @@ from modules.system import *
def auto_response(message, snr, rssi, hop, message_from_id, channel_number, deviceID):
# Auto response to messages
if "ping" in message.lower():
# Check if the user added @foo to the message
if "@" in message:
if hop == "Direct":
bot_response = "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}" + " and copy: " + message.split("@")[1]
else:
bot_response = "🏓PONG, " + hop + " and copy: " + message.split("@")[1]
else:
if hop == "Direct":
bot_response = "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}"
else:
bot_response = "🏓PONG, " + hop
elif "pong" in message.lower():
bot_response = "🏓Ping!!"
elif "motd" in message.lower():
# check if the user wants to set the motd by using $
if "$" in message:
motd = message.split("$")[1]
global MOTD
MOTD = motd
bot_response = "MOTD Set to: " + MOTD
else:
bot_response = MOTD
elif "cmd" in message.lower() or "cmd?" in message.lower():
bot_response = help_message
elif "lheard" in message.lower() or "sitrep" in message.lower():
bot_response = "Last heard:\n" + str(get_node_list(1))
chutil1 = interface1.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil1 = "{:.2f}".format(chutil1)
if interface2_enabled:
bot_response += "Port2:\n" + str(get_node_list(2))
chutil2 = interface2.nodes.get(decimal_to_hex(myNodeNum2), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil2 = "{:.2f}".format(chutil2)
elif "ack" in message.lower():
if hop == "Direct":
bot_response = "🏓ACK-ACK! " + f"SNR:{snr} RSSI:{rssi}"
else:
bot_response = "🏓ACK-ACK! " + hop
elif "testing" in message.lower() or "test" in message.lower():
if hop == "Direct":
bot_response = "🏓Testing 1,2,3 " + f"SNR:{snr} RSSI:{rssi}"
else:
bot_response = "🏓Testing 1,2,3 " + hop
else:
bot_response = "I'm sorry, I'm afraid I can't do that."
message_lower = message.lower()
bot_response = "I'm sorry, I'm afraid I can't do that."
command_handler = {
"ping": lambda: handle_ping(message, hop, snr, rssi),
"pong": lambda: "🏓Ping!!",
"motd": lambda: handle_motd(message, MOTD),
"cmd": lambda: help_message,
"cmd?": lambda: help_message,
"lheard": lambda: handle_lheard(interface1, interface2_enabled, myNodeNum1, myNodeNum2),
"sitrep": lambda: handle_lheard(interface1, interface2_enabled, myNodeNum1, myNodeNum2),
"ack": lambda: handle_ack(hop, snr, rssi),
"testing": lambda: handle_testing(hop, snr, rssi),
"test": lambda: handle_testing(hop, snr, rssi),
}
cmds = [] # list to hold the commands found in the message
for key in command_handler:
if key in message_lower.split(' '):
cmds.append({'cmd': key, 'index': message_lower.index(key)})
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}")
# run the first command after sorting
bot_response = command_handler[cmds[0]['cmd']]()
# wait a 700ms to avoid message collision from lora-ack
time.sleep(0.7)
return bot_response
def handle_ping(message, hop, snr, rssi):
if "@" in message:
if hop == "Direct":
return "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}" + " and copy: " + message.split("@")[1]
else:
return "🏓PONG, " + hop + " and copy: " + message.split("@")[1]
else:
if hop == "Direct":
return "🏓PONG, " + f"SNR:{snr} RSSI:{rssi}"
else:
return "🏓PONG, " + hop
def handle_motd(message):
global MOTD
if "$" in message:
motd = message.split("$")[1]
MOTD = motd.rstrip()
return "MOTD Set to: " + MOTD
else:
return MOTD
def handle_lheard(interface1, interface2_enabled, myNodeNum1, myNodeNum2):
bot_response = "Last heard:\n" + str(get_node_list(1))
chutil1 = interface1.nodes.get(decimal_to_hex(myNodeNum1), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil1 = "{:.2f}".format(chutil1)
if interface2_enabled:
bot_response += "Port2:\n" + str(get_node_list(2))
chutil2 = interface2.nodes.get(decimal_to_hex(myNodeNum2), {}).get("deviceMetrics", {}).get("channelUtilization", 0)
chutil2 = "{:.2f}".format(chutil2)
return bot_response
def handle_ack(hop, snr, rssi):
if hop == "Direct":
return "🏓ACK-ACK! " + f"SNR:{snr} RSSI:{rssi}"
else:
return "🏓ACK-ACK! " + hop
def handle_testing(hop, snr, rssi):
if hop == "Direct":
return "🏓Testing 1,2,3 " + f"SNR:{snr} RSSI:{rssi}"
else:
return "🏓Testing 1,2,3 " + hop
def onReceive(packet, interface):
# extract interface defailts from interface object
rxType = type(interface).__name__