mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-03-28 17:32:36 +01:00
Compare commits
24 Commits
v1.4.8hotf
...
v1.4.9
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
249ee3bb5a | ||
|
|
a3b3d4ea0e | ||
|
|
27f9d04538 | ||
|
|
03f1869b23 | ||
|
|
479e177a64 | ||
|
|
5cf166af87 | ||
|
|
e24bcd7d38 | ||
|
|
768898df64 | ||
|
|
cf282e04bb | ||
|
|
db4edac083 | ||
|
|
877d0cf7f8 | ||
|
|
e78c441a6e | ||
|
|
e945819365 | ||
|
|
23e8db50fd | ||
|
|
193ffe6394 | ||
|
|
87016186d8 | ||
|
|
d7d96a89cf | ||
|
|
aa5ef23363 | ||
|
|
c18e0401e4 | ||
|
|
8568990295 | ||
|
|
44e6460224 | ||
|
|
d53480290c | ||
|
|
1499d883bc | ||
|
|
883a6902fa |
11
README.md
11
README.md
@@ -10,6 +10,10 @@ Welcome to the Mesh Bot project! This feature-rich bot is designed to enhance yo
|
||||
- **Automated Responses**: The bot traps keywords like "ping" and responds with "pong" in direct messages (DMs) or group channels.
|
||||
- **Customizable Triggers**: Monitor group channels for specific keywords and set custom responses.
|
||||
|
||||
### Network Tools
|
||||
- **Enhance and build local mesh**: Ping allow for message delivery testing with more realistic packets vs. telemetry
|
||||
- **Test node hardware**: `test` will send incremental data into the radio buffer for overall length of message testing
|
||||
|
||||
### Dual Radio/Node Support
|
||||
- **Simultaneous Monitoring**: Monitor two networks at the same time.
|
||||
- **Flexible Messaging**: send mail and messages, between networks.
|
||||
@@ -246,7 +250,7 @@ rtl_fm -f 162425000 -s 22050 | multimon-ng -t raw -a EAS /dev/stdin | python eas
|
||||
```
|
||||
|
||||
### Scheduler
|
||||
The Scheduler is enabled in the `settings.py` by setting `scheduler_enabled = True`. The actions and settings are via code only at this time. See mesh_bot.py around line [425](https://github.com/SpudGunMan/meshing-around/blob/22983133ee4db3df34f66699f565e506de296197/mesh_bot.py#L425-L435) to edit the schedule. See [schedule documentation](https://schedule.readthedocs.io/en/stable/) for more.
|
||||
The Scheduler is enabled in the `settings.py` by setting `scheduler_enabled = True`. The actions and settings are via code only at this time. See mesh_bot.py around line [1050](https://github.com/SpudGunMan/meshing-around/blob/e94581936530c76ea43500eebb43f32ba7ed5e19/mesh_bot.py#L1050) to edit the schedule. See [schedule documentation](https://schedule.readthedocs.io/en/stable/) for more.
|
||||
|
||||
```python
|
||||
#Send WX every Morning at 08:00 using handle_wxc function to channel 2 on device 1
|
||||
@@ -314,7 +318,8 @@ sudo apt-get install fonts-noto-color-emoji
|
||||
### Networking
|
||||
| Command | Description | ✅ Works Off-Grid |
|
||||
|---------|-------------|-
|
||||
| `ping`, `ack`, `test` | Return data for signal. Example: `ping 15 #DrivingI5` (activates auto-ping every 20 seconds for count 15) | ✅ |
|
||||
| `ping`, `ack` | Return data for signal. Example: `ping 15 #DrivingI5` (activates auto-ping every 20 seconds for count 15) | ✅ |
|
||||
| `test` | Returns like ping but also can be used to test the limits of data buffers `test 4` sends data to the maxBuffer limit (default 225) | ✅ |
|
||||
| `whereami` | Returns the address of the sender's location if known |
|
||||
| `whoami` | Returns details of the node asking, also returned when position exchanged 📍 | ✅ |
|
||||
| `motd` | Displays the message of the day or sets it. Example: `motd $New Message Of the day` | ✅ |
|
||||
@@ -385,7 +390,7 @@ I used ideas and snippets from other responder bots and want to call them out!
|
||||
- **xdep**: For the reporting tools.
|
||||
- **Nestpebble**: For new ideas and enhancements.
|
||||
- **mrpatrick1991**: For Docker configurations.
|
||||
- **Mike O'Connell/skrrt**: For [text](etc/eas_alert_parser.py) enhanced by **sheer.cold**
|
||||
- **Mike O'Connell/skrrt**: For [eas_alert_parser](etc/eas_alert_parser.py) enhanced by **sheer.cold**
|
||||
- **PiDiBi**: For looking at test functions and other suggestions like wxc, CPU use, and alerting ideas.
|
||||
- **Cisien, bitflip, **Woof**, and Hailo1999**: For testing and feature ideas on Discord and GitHub.
|
||||
- **Meshtastic Discord Community**: For tossing out ideas and testing code.
|
||||
|
||||
@@ -36,6 +36,7 @@ welcome_message = MeshBot, here for you like a friend who is not. Try sending: p
|
||||
whoami = True
|
||||
# enable or disable the Joke module
|
||||
DadJokes = True
|
||||
DadJokesEmoji = False
|
||||
# enable or disable the Solar module
|
||||
spaceWeather = True
|
||||
# enable or disable the wikipedia search module
|
||||
@@ -149,4 +150,9 @@ responseDelay = 0.7
|
||||
splitDelay = 0.0
|
||||
# message chunk size for sending at high success rate
|
||||
MESSAGE_CHUNK_SIZE = 160
|
||||
# Request Acknowledgement of message OTA
|
||||
wantAck = False
|
||||
# Max lilmit Buffer for radio testing
|
||||
maxBuffer = 220
|
||||
|
||||
|
||||
|
||||
16
mesh_bot.py
16
mesh_bot.py
@@ -120,7 +120,7 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann
|
||||
|
||||
if "ping" in message.lower():
|
||||
msg = "🏓PONG\n"
|
||||
type = "🏓PING\n"
|
||||
type = "🏓PING"
|
||||
elif "test" in message.lower() or "testing" in message.lower():
|
||||
msg = random.choice(["🎙Testing 1,2,3\n", "🎙Testing\n",\
|
||||
"🎙Testing, testing\n",\
|
||||
@@ -159,6 +159,8 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann
|
||||
if multiPingList[i].get('message_from_id') == message_from_id:
|
||||
multiPingList.pop(i)
|
||||
msg = "🛑 auto-ping"
|
||||
|
||||
# set inital pingCount
|
||||
try:
|
||||
pingCount = int(message.split(" ")[1])
|
||||
if pingCount == 123 or pingCount == 1234:
|
||||
@@ -169,8 +171,11 @@ def handle_ping(message_from_id, deviceID, message, hop, snr, rssi, isDM, chann
|
||||
pingCount = -1
|
||||
|
||||
if pingCount > 1:
|
||||
multiPingList.append({'message_from_id': message_from_id, 'count': pingCount + 1, 'type': type, 'deviceID': deviceID, 'channel_number': channel_number})
|
||||
msg = f"🚦Initalizing {pingCount} auto-ping"
|
||||
multiPingList.append({'message_from_id': message_from_id, 'count': pingCount + 1, 'type': type, 'deviceID': deviceID, 'channel_number': channel_number, 'startCount': pingCount})
|
||||
if type == "🎙TEST":
|
||||
msg = f"🛜Initalizing BufferTest, using chunks of about {int(maxBuffer // pingCount)}, max length {maxBuffer} in {pingCount} messages"
|
||||
else:
|
||||
msg = f"🚦Initalizing {pingCount} auto-ping"
|
||||
|
||||
return msg
|
||||
|
||||
@@ -879,6 +884,7 @@ def onReceive(packet, interface):
|
||||
|
||||
if hop_start == hop_limit:
|
||||
hop = "Direct"
|
||||
hop_count = 0
|
||||
else:
|
||||
# set hop to Direct if the message was sent directly otherwise set the hop count
|
||||
if hop_away > 0:
|
||||
@@ -1066,8 +1072,8 @@ async def start_rx():
|
||||
# Send the MOTD every day at 13:00 using send_message function to channel 2 on device 1
|
||||
#schedule.every().day.at("13:00").do(lambda: send_message(MOTD, 2, 0, 1))
|
||||
|
||||
# Send bbslink looking for peers every other day at 10:00 using send_message function to channel 0 on device 1
|
||||
#schedule.every(2).days.at("10:00").do(lambda: send_message("bbslink MeshBot looking for peers", 0, 0, 1))
|
||||
# Send bbslink looking for peers every other day at 10:00 using send_message function to channel 3 on device 1
|
||||
#schedule.every(2).days.at("10:00").do(lambda: send_message("bbslink MeshBot looking for peers", 3, 0, 1))
|
||||
|
||||
#
|
||||
logger.debug("System: Starting the broadcast scheduler")
|
||||
|
||||
@@ -117,6 +117,10 @@ def sendWithEmoji(message):
|
||||
|
||||
def tell_joke(nodeID=0):
|
||||
dadjoke = Dadjoke()
|
||||
renderedLaugh = sendWithEmoji(dadjoke.joke)
|
||||
|
||||
if dad_jokes_emojiJokes:
|
||||
renderedLaugh = sendWithEmoji(dadjoke.joke)
|
||||
else:
|
||||
renderedLaugh = dadjoke.joke
|
||||
return renderedLaugh
|
||||
|
||||
|
||||
@@ -379,7 +379,7 @@ def alertBrodcast():
|
||||
global wxAlertCache
|
||||
currentAlert = getWeatherAlerts(latitudeValue, longitudeValue)
|
||||
|
||||
if currentAlert[0] == ERROR_FETCHING_DATA or currentAlert == NO_DATA_NOGPS or currentAlert == NO_ALERTS:
|
||||
if currentAlert == ERROR_FETCHING_DATA or currentAlert == NO_DATA_NOGPS or currentAlert == NO_ALERTS:
|
||||
wxAlertCache = ""
|
||||
return False
|
||||
# broadcast the alerts send to wxBrodcastCh
|
||||
|
||||
@@ -115,6 +115,7 @@ try:
|
||||
lheardCmdIgnoreNode = config['general'].get('lheardCmdIgnoreNode', '').split(',')
|
||||
whoami_enabled = config['general'].getboolean('whoami', True)
|
||||
dad_jokes_enabled = config['general'].getboolean('DadJokes', False)
|
||||
dad_jokes_emojiJokes = config['general'].getboolean('DadJokesEmoji', False)
|
||||
solar_conditions_enabled = config['general'].getboolean('spaceWeather', True)
|
||||
wikipedia_enabled = config['general'].getboolean('wikipedia', False)
|
||||
llm_enabled = config['general'].getboolean('ollama', False) # https://ollama.com
|
||||
@@ -184,6 +185,8 @@ try:
|
||||
responseDelay = config['messagingSettings'].getfloat('responseDelay', 0.7) # default 0.7
|
||||
splitDelay = config['messagingSettings'].getfloat('splitDelay', 0) # default 0
|
||||
MESSAGE_CHUNK_SIZE = config['messagingSettings'].getint('MESSAGE_CHUNK_SIZE', 160) # default 160
|
||||
wantAck = config['messagingSettings'].getboolean('wantAck', False) # default False
|
||||
maxBuffer = config['messagingSettings'].getint('maxBuffer', 220) # default 220
|
||||
|
||||
except KeyError as e:
|
||||
print(f"System: Error reading config file: {e}")
|
||||
|
||||
@@ -6,6 +6,7 @@ import meshtastic.tcp_interface
|
||||
import meshtastic.ble_interface
|
||||
import time
|
||||
import asyncio
|
||||
import random
|
||||
import contextlib # for suppressing output on watchdog
|
||||
import io # for suppressing output on watchdog
|
||||
from modules.log import *
|
||||
@@ -15,7 +16,7 @@ trap_list = ("cmd","cmd?") # default trap list
|
||||
help_message = "Bot CMD?:\n"
|
||||
asyncLoop = asyncio.new_event_loop()
|
||||
games_enabled = False
|
||||
multiPingList = [{'message_from_id': 0, 'count': 0, 'type': '', 'deviceID': 0, 'channel_number': 0}]
|
||||
multiPingList = [{'message_from_id': 0, 'count': 0, 'type': '', 'deviceID': 0, 'channel_number': 0, 'startCount': 0}]
|
||||
|
||||
|
||||
# Ping Configuration
|
||||
@@ -461,12 +462,18 @@ def messageChunker(message):
|
||||
|
||||
return message
|
||||
|
||||
def send_message(message, ch, nodeid=0, nodeInt=1):
|
||||
def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False):
|
||||
# Send a message to a channel or DM
|
||||
interface = interface1 if nodeInt == 1 else interface2
|
||||
# Check if the message is empty
|
||||
if message == "" or message == None or len(message) == 0:
|
||||
return False
|
||||
interface = interface1 if nodeInt == 1 else interface2
|
||||
# Split the message into chunks if it exceeds the MESSAGE_CHUNK_SIZE
|
||||
message_list = messageChunker(message)
|
||||
|
||||
if not bypassChuncking:
|
||||
# Split the message into chunks if it exceeds the MESSAGE_CHUNK_SIZE
|
||||
message_list = messageChunker(message)
|
||||
else:
|
||||
message_list = [message]
|
||||
|
||||
if isinstance(message_list, list):
|
||||
# Send the message to the channel or DM
|
||||
@@ -476,13 +483,22 @@ def send_message(message, ch, nodeid=0, nodeInt=1):
|
||||
chunkOf = f"{message_list.index(m)+1}/{num_chunks}"
|
||||
if nodeid == 0:
|
||||
# Send to channel
|
||||
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + f"Chunker{chunkOf} SendingChannel: " + CustomFormatter.white + m.replace('\n', ' '))
|
||||
interface.sendText(text=m, channelIndex=ch)
|
||||
if wantAck:
|
||||
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + f"req.ACK " + "Chunker{chunkOf} SendingChannel: " + CustomFormatter.white + m.replace('\n', ' '))
|
||||
interface.sendText(text=m, channelIndex=ch, wantAck=True)
|
||||
else:
|
||||
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + f"Chunker{chunkOf} SendingChannel: " + CustomFormatter.white + m.replace('\n', ' '))
|
||||
interface.sendText(text=m, channelIndex=ch)
|
||||
else:
|
||||
# Send to DM
|
||||
logger.info(f"Device:{nodeInt} " + CustomFormatter.red + f"Chunker{chunkOf} Sending DM: " + CustomFormatter.white + m.replace('\n', ' ') + CustomFormatter.purple +\
|
||||
if wantAck:
|
||||
logger.info(f"Device:{nodeInt} " + CustomFormatter.red + f"req.ACK " + f"Chunker{chunkOf} Sending DM: " + CustomFormatter.white + m.replace('\n', ' ') + CustomFormatter.purple +\
|
||||
" To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}")
|
||||
interface.sendText(text=m, channelIndex=ch, destinationId=nodeid)
|
||||
interface.sendText(text=m, channelIndex=ch, destinationId=nodeid, wantAck=True)
|
||||
else:
|
||||
logger.info(f"Device:{nodeInt} " + CustomFormatter.red + f"Chunker{chunkOf} Sending DM: " + CustomFormatter.white + m.replace('\n', ' ') + CustomFormatter.purple +\
|
||||
" To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}")
|
||||
interface.sendText(text=m, channelIndex=ch, destinationId=nodeid)
|
||||
|
||||
# Throttle the message sending to prevent spamming the device
|
||||
if (message_list.index(m)+1) % 4 == 0:
|
||||
@@ -496,13 +512,22 @@ def send_message(message, ch, nodeid=0, nodeInt=1):
|
||||
else: # message is less than MESSAGE_CHUNK_SIZE characters
|
||||
if nodeid == 0:
|
||||
# Send to channel
|
||||
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + "SendingChannel: " + CustomFormatter.white + message.replace('\n', ' '))
|
||||
interface.sendText(text=message, channelIndex=ch)
|
||||
if wantAck:
|
||||
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + "req.ACK " + "SendingChannel: " + CustomFormatter.white + message.replace('\n', ' '))
|
||||
interface.sendText(text=message, channelIndex=ch, wantAck=True)
|
||||
else:
|
||||
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + "SendingChannel: " + CustomFormatter.white + message.replace('\n', ' '))
|
||||
interface.sendText(text=message, channelIndex=ch)
|
||||
else:
|
||||
# Send to DM
|
||||
logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending DM: " + CustomFormatter.white + message.replace('\n', ' ') + CustomFormatter.purple +\
|
||||
" To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}")
|
||||
interface.sendText(text=message, channelIndex=ch, destinationId=nodeid)
|
||||
if wantAck:
|
||||
logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "req.ACK " + "Sending DM: " + CustomFormatter.white + message.replace('\n', ' ') + CustomFormatter.purple +\
|
||||
" To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}")
|
||||
interface.sendText(text=message, channelIndex=ch, destinationId=nodeid, wantAck=True)
|
||||
else:
|
||||
logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending DM: " + CustomFormatter.white + message.replace('\n', ' ') + CustomFormatter.purple +\
|
||||
" To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}")
|
||||
interface.sendText(text=message, channelIndex=ch, destinationId=nodeid)
|
||||
return True
|
||||
|
||||
def get_wikipedia_summary(search_term):
|
||||
@@ -573,9 +598,10 @@ def handleMultiPing(nodeID=0, deviceID=1):
|
||||
for i in range(len(mPlCpy)):
|
||||
message_id_from = mPlCpy[i]['message_from_id']
|
||||
count = mPlCpy[i]['count']
|
||||
type = mPlCpy[i]['type'].strip()
|
||||
type = mPlCpy[i]['type']
|
||||
deviceID = mPlCpy[i]['deviceID']
|
||||
channel_number = mPlCpy[i]['channel_number']
|
||||
start_count = mPlCpy[i]['startCount']
|
||||
|
||||
if count > 1:
|
||||
count -= 1
|
||||
@@ -584,7 +610,28 @@ def handleMultiPing(nodeID=0, deviceID=1):
|
||||
if multiPingList[i]['message_from_id'] == message_id_from:
|
||||
multiPingList[i]['count'] = count
|
||||
|
||||
send_message(f"🔂{count} {type}", channel_number, message_id_from, deviceID)
|
||||
# handle bufferTest
|
||||
if type == '🎙TEST':
|
||||
buffer = ''.join(random.choice(['0', '1']) for i in range(maxBuffer))
|
||||
# divide buffer by start_count and get resolution
|
||||
resolution = maxBuffer // start_count
|
||||
slice = resolution * count
|
||||
if slice > maxBuffer:
|
||||
slice = maxBuffer
|
||||
# set the type as a portion of the buffer
|
||||
type = buffer[slice - resolution:]
|
||||
# if exceed the maxBuffer, remove the excess
|
||||
count = len(type + "🔂 ")
|
||||
if count > maxBuffer:
|
||||
type = type[:maxBuffer - count]
|
||||
# final length count of the message for display
|
||||
count = len(type + "🔂 ")
|
||||
if count < 99:
|
||||
count -= 1
|
||||
|
||||
# send the DM
|
||||
send_message(f"🔂{count} {type}", channel_number, message_id_from, deviceID, bypassChuncking=True)
|
||||
time.sleep(responseDelay + 1)
|
||||
if count < 2:
|
||||
# remove the item from the list
|
||||
for j in range(len(multiPingList)):
|
||||
|
||||
Reference in New Issue
Block a user