From 5cc46fed8f9b8714097a2d4e9739e114b7f4423a Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 6 Aug 2024 13:04:05 -0700 Subject: [PATCH 1/5] colog colors and logging --- mesh_bot.py | 81 ++++++++++++++++++---------------- modules/bbstools.py | 18 ++++---- modules/log.py | 59 +++++++++++++++++++++++++ modules/settings.py | 1 - modules/system.py | 105 +++++++++++++++++++++----------------------- pong_bot.py | 50 +++++++++++++-------- 6 files changed, 191 insertions(+), 123 deletions(-) create mode 100644 modules/log.py diff --git a/mesh_bot.py b/mesh_bot.py index 7f04926..d9fc5ee 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -5,7 +5,7 @@ import asyncio import time # for sleep, get some when you can :) from pubsub import pub # pip install pubsub -from modules.settings import * +from modules.log import * from modules.system import * def auto_response(message, snr, rssi, hop, message_from_id, channel_number, deviceID): @@ -110,7 +110,7 @@ def auto_response(message, snr, rssi, hop, message_from_id, channel_number, devi if "#" in message: body = message.split("#")[1] body = body.rstrip() - print(f"{log_timestamp()} System: BBS Post: {subject} Body: {body}") + 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" @@ -196,7 +196,7 @@ def onReceive(packet, interface): if msg: # wait a 700ms to avoid message collision from lora-ack. time.sleep(0.7) - print(f"{log_timestamp()} System: BBS DM Found: {msg[1]} For: {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.info(f"System: BBS DM Found: {msg[1]} For: {get_name_from_number(message_from_id, 'long', rxNode)}") message = "Mail: " + msg[1] + " From: " + get_name_from_number(msg[2], 'long', rxNode) bbs_delete_dm(msg[0], msg[1]) send_message(message, channel_number, message_from_id, rxNode) @@ -249,26 +249,29 @@ def onReceive(packet, interface): if message_string == help_message or message_string == welcome_message or "CMD?:" in message_string: # ignore help and welcome messages - print(f"{log_timestamp()} Got Own Welcome/Help header. From: {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.warning(f"Got Own Welcome/Help header. From: {get_name_from_number(message_from_id, 'long', rxNode)}") return # If the packet is a DM (Direct Message) respond to it, otherwise validate its a message for us on the channel if packet['to'] == myNodeNum1 or packet['to'] == myNodeNum2: # message is DM to us - # check if the message contains a trap word, DMs are always responded to if messageTrap(message_string): - print(f"{log_timestamp()} Received DM: {message_string} on Device:{rxNode} Channel: {channel_number} From: {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") # respond with DM send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) else: # respond with welcome message on DM - print(f"{log_timestamp()} Ignoring DM: {message_string} on Device:{rxNode} From: {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.warning(f"Device:{rxNode} Ignoring DM: {message_string} From: {get_name_from_number(message_from_id, 'long', rxNode)}") send_message(welcome_message, channel_number, message_from_id, rxNode) + msgLogger.info(f"{get_name_from_number(message_from_id, 'long', rxNode)} | {message_string}") else: # message is on a channel if messageTrap(message_string): - print(f"{log_timestamp()} Received On Device:{rxNode} Channel {channel_number}: {message_string} From: {get_name_from_number(message_from_id, 'long', rxNode)}") + # message is for bot to respond to + logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") if useDMForResponse: # respond to channel message via direct message send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) @@ -276,7 +279,7 @@ def onReceive(packet, interface): # or respond to channel message on the channel itself if channel_number == publicChannel and antiSpam: # warning user spamming default channel - print(f"{log_timestamp()} System: Warning spamming default channel not allowed. sending DM to {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.error(f"System: Warning spamming default channel not allowed. sending DM to {get_name_from_number(message_from_id, 'long', rxNode)}") # respond to channel message via direct message send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) @@ -284,8 +287,8 @@ def onReceive(packet, interface): # respond to channel message on the channel itself 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 - # add the message to the message history but limit if zuluTime: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") else: @@ -306,47 +309,47 @@ def onReceive(packet, interface): # if channel found in the repeater list repeat the message if str(channel_number) in repeater_channels: if rxNode == 1: - print(f"{log_timestamp()} Repeating message on Device2 Channel:{channel_number}") + logger.debug(f"Repeating message on Device2 Channel:{channel_number}") send_message(rMsg, channel_number, 0, 2) elif rxNode == 2: - print(f"{log_timestamp()} Repeating message on Device1 Channel:{channel_number}") + logger.debug(f"Repeating message on Device1 Channel:{channel_number}") send_message(rMsg, channel_number, 0, 1) + msgLogger.info(f"{get_name_from_number(message_from_id, 'long', rxNode)} | {message_string}") else: - print(f"{log_timestamp()} System: Ignoring incoming Device:{rxNode} Channel:{channel_number} Message: {message_string} From: {get_name_from_number(message_from_id)}") + # nothing to do for us + logger.info(f"Ignoring incoming Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Message:" + CustomFormatter.white +\ + f" {message_string} " + CustomFormatter.yellow + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}") + msgLogger.info(f"{get_name_from_number(message_from_id, 'long', rxNode)} | {message_string}") except KeyError as e: - print(f"{log_timestamp()} System: Error processing packet: {e} Device:{rxNode}") + logger.critical(f"System: Error processing packet: {e} Device:{rxNode}") print(packet) # print the packet for debugging print("END of packet \n") async def start_rx(): - print ("\nMeshtastic Autoresponder Bot CTL+C to exit\n") - if bbs_enabled: - print(f"System: BBS Enabled, {bbsdb} has {len(bbs_messages)} messages. Direct Mail Messages waiting: {(len(bbs_dm) - 1)}") - if solar_conditions_enabled: - print(f"System: Celestial Telemetry Enabled") - if location_enabled: - print(f"System: Location Telemetry Enabled") - if dad_jokes_enabled: - print(f"System: Dad Jokes Enabled!") - if store_forward_enabled: - print(f"System: Store and Forward Enabled using limit: {storeFlimit}") - if useDMForResponse: - print(f"System: Respond by DM only") - if repeater_enabled and interface2_enabled: - print(f"System: Repeater Enabled for Channels: {repeater_channels}") - if radio_dectection_enabled: - print(f"System: Radio Detection Enabled using rigctld at {rigControlServerAddress} brodcasting to channels: {sigWatchBrodcastCh} for {get_freq_common_name(get_hamlib('f'))}") - + 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') - - msg = (f"{log_timestamp()} 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)}") - print (msg) + 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: - msg = (f"{log_timestamp()} System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)}," - f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}") - print (msg) + logger.info(f"System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)}," + f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}") + 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: + logger.debug(f"System: Celestial Telemetry Enabled") + if location_enabled: + logger.debug(f"System: Location Telemetry Enabled") + if dad_jokes_enabled: + logger.debug(f"System: Dad Jokes Enabled!") + if store_forward_enabled: + logger.debug(f"System: Store and Forward Enabled using limit: {storeFlimit}") + if useDMForResponse: + logger.debug(f"System: Respond by DM only") + if repeater_enabled and interface2_enabled: + 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'))}") # here we go loopty loo while True: diff --git a/modules/bbstools.py b/modules/bbstools.py index e74753e..68264d1 100644 --- a/modules/bbstools.py +++ b/modules/bbstools.py @@ -2,7 +2,7 @@ # K7MHI Kelly Keeton 2024 import pickle # pip install pickle -from modules.settings import * +from modules.log import * trap_list_bbs = ("bbslist", "bbspost", "bbsread", "bbsdelete", "bbshelp") @@ -18,14 +18,14 @@ def load_bbsdb(): bbs_messages = pickle.load(f) except: bbs_messages = [[1, "Welcome to meshBBS", "Welcome to the BBS, please post a message!",0]] - print ("\nSystem: Creating new bbsdb.pkl") + logger.debug("\nSystem: Creating new bbsdb.pkl") with open('bbsdb.pkl', 'wb') as f: pickle.dump(bbs_messages, f) def save_bbsdb(): global bbs_messages # save the bbs messages to the database file - print ("System: Saving bbsdb.pkl\n") + logger.debug("System: Saving bbsdb.pkl\n") with open('bbsdb.pkl', 'wb') as f: pickle.dump(bbs_messages, f) @@ -64,7 +64,7 @@ def bbs_delete_message(messageID = 0, fromNode = 0): return "Msg #" + str(messageID) + " deleted." else: - print (f"!!System: node {fromNode}, tried to delete a message: {bbs_messages[messageID - 1]} and was dropped.") + logger.warning(f"System: node {fromNode}, tried to delete a message: {bbs_messages[messageID - 1]} and was dropped.") return "You are not authorized to delete this message." else: return "Please specify a message number to delete." @@ -75,12 +75,12 @@ def bbs_post_message(subject, message, fromNode): # Check the BAN list for naughty nodes and silently drop the message if str(fromNode) in bbs_ban_list: - print (f"!!System: Naughty node {fromNode}, tried to post a message: {subject}, {message} and was dropped.") + logger.warning(f"System: Naughty node {fromNode}, tried to post a message: {subject}, {message} and was dropped.") return "Message posted. ID is: " + str(messageID) # append the message to the list bbs_messages.append([messageID, subject, message, fromNode]) - print (f"System: NEW Message Posted, subject: {subject}, message: {message} from {fromNode}") + logger.info(f"System: NEW Message Posted, subject: {subject}, message: {message} from {fromNode}") # save the bbsdb save_bbsdb() @@ -100,7 +100,7 @@ def bbs_read_message(messageID = 0): def save_bbsdm(): global bbs_dm # save the bbs messages to the database file - print ("System: Saving Updated BBS Direct Messages bbsdm.pkl") + logger.debug("System: Saving Updated BBS Direct Messages bbsdm.pkl") with open('bbsdm.pkl', 'wb') as f: pickle.dump(bbs_dm, f) @@ -112,7 +112,7 @@ def load_bbsdm(): bbs_dm = pickle.load(f) except: bbs_dm = [[1234567890, "Message", 1234567890]] - print ("\nSystem: Creating new bbsdm.pkl") + logger.debug("\nSystem: Creating new bbsdm.pkl") with open('bbsdm.pkl', 'wb') as f: pickle.dump(bbs_dm, f) @@ -120,7 +120,7 @@ def bbs_post_dm(toNode, message, fromNode): global bbs_dm # Check the BAN list for naughty nodes and silently drop the message if str(fromNode) in bbs_ban_list: - print (f"!!System: Naughty node {fromNode}, tried to post a message: {message} and was dropped.") + logger.warning(f"System: Naughty node {fromNode}, tried to post a message: {message} and was dropped.") return "DM Posted for node " + str(toNode) # append the message to the list diff --git a/modules/log.py b/modules/log.py new file mode 100644 index 0000000..ff1bb7d --- /dev/null +++ b/modules/log.py @@ -0,0 +1,59 @@ +import logging +from datetime import datetime +from modules.settings import * + +class CustomFormatter(logging.Formatter): + grey = '\x1b[38;21m' + white = '\x1b[38;5;231m' + blue = '\x1b[38;5;39m' + yellow = '\x1b[38;5;226m' + red = '\x1b[38;5;196m' + green = '\x1b[38;5;46m' + purple = '\x1b[38;5;129m' + bold_red = '\x1b[31;1m' + bold_white = '\x1b[37;1m' + reset = '\x1b[0m' + + def __init__(self, fmt): + super().__init__() + self.fmt = fmt + self.FORMATS = { + logging.DEBUG: self.blue + self.fmt + self.reset, + logging.INFO: self.white + self.fmt + self.reset, + logging.WARNING: self.yellow + self.fmt + self.reset, + logging.ERROR: self.red + self.fmt + self.reset, + logging.CRITICAL: self.bold_red + self.fmt + self.reset + } + + def format(self, record): + log_fmt = self.FORMATS.get(record.levelno) + formatter = logging.Formatter(log_fmt) + return formatter.format(record) + +# Create logger +logger = logging.getLogger("MeshBot System Logger") +logger.setLevel(logging.DEBUG) +logger.propagate = False + +msgLogger = logging.getLogger("MeshBot Messages Logger") +msgLogger.setLevel(logging.INFO) +msgLogger.propagate = False + +# Define format for logs +logFormat = '%(asctime)s | %(levelname)8s | %(message)s' +msgLogFormat = '%(asctime)s | %(message)s' + +# Create stdout handler for logging to the console +stdout_handler = logging.StreamHandler() +stdout_handler.setLevel(logging.DEBUG) +stdout_handler.setFormatter(CustomFormatter(logFormat)) + +# Create file handler for logging to a file (logs INFO level and above) +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) +msgLogger.addHandler(file_handler) diff --git a/modules/settings.py b/modules/settings.py index 226347d..2189db0 100644 --- a/modules/settings.py +++ b/modules/settings.py @@ -92,4 +92,3 @@ except KeyError as e: print(f"System: Check the config.ini against config.template file for missing sections or values.") print(f"System: Exiting...") exit(1) - diff --git a/modules/system.py b/modules/system.py index c12b7df..05778a0 100644 --- a/modules/system.py +++ b/modules/system.py @@ -4,10 +4,9 @@ import meshtastic.serial_interface #pip install meshtastic import meshtastic.tcp_interface import meshtastic.ble_interface -from datetime import datetime import time import asyncio -from modules.settings import * +from modules.log import * # Global Variables trap_list = ("cmd","cmd?") # default trap list @@ -68,10 +67,10 @@ try: elif interface1_type == 'ble': interface1 = meshtastic.ble_interface.BLEInterface(mac1) else: - print(f"System: Interface Type: {interface1_type} not supported. Validate your config against config.template Exiting") + logger.critical(f"System: Interface Type: {interface1_type} not supported. Validate your config against config.template Exiting") exit() except Exception as e: - print(f"System: Critical Error script abort. Initalizing Interface1 {e}") + logger.critical(f"System: script abort. Initalizing Interface1 {e}") exit() # Interface2 Configuration @@ -84,10 +83,10 @@ if interface2_enabled: elif interface2_type == 'ble': interface2 = meshtastic.ble_interface.BLEInterface(mac2) else: - print(f"System: Interface Type: {interface2_type} not supported. Validate your config against config.template Exiting") + logger.critical(f"System: Interface Type: {interface2_type} not supported. Validate your config against config.template Exiting") exit() except Exception as e: - print(f"System: Critical Error script abort. Initalizing Interface2 {e}") + logger.critical(f"System: script abort. Initalizing Interface2 {e}") exit() #Get the node number of the device, check if the device is connected @@ -95,7 +94,7 @@ try: myinfo = interface1.getMyNodeInfo() myNodeNum1 = myinfo['num'] except Exception as e: - print(f"System: Critical Error script abort. {e}") + logger.critical(f"System: script abort. {e}") exit() if interface2_enabled: @@ -103,17 +102,11 @@ if interface2_enabled: myinfo2 = interface2.getMyNodeInfo() myNodeNum2 = myinfo2['num'] except Exception as e: - print(f"System: Critical Error script abort. {e}") + logger.critical(f"System: script abort. {e}") exit() else: myNodeNum2 = 777 -def log_timestamp(): - if zuluTime: - return datetime.now().strftime("%Y-%m-%d %H:%M:%S") - else: - return datetime.now().strftime("%Y-%m-%d %I:%M:%S%p") - def decimal_to_hex(decimal_number): return f"!{decimal_number:08x}" @@ -171,7 +164,7 @@ def get_node_list(nodeInt=1): item = (node_name, last_heard, snr) node_list1.append(item) else: - print (f"{log_timestamp()} System: No nodes found") + logger.warning(f"System: No nodes found") return ERROR_FETCHING_DATA if nodeInt == 2: @@ -189,7 +182,7 @@ def get_node_list(nodeInt=1): item = (node_name, last_heard, snr) node_list2.append(item) else: - print (f"{log_timestamp()} System: No nodes found") + logger.warning(f"System: No nodes found") return ERROR_FETCHING_DATA node_list1.sort(key=lambda x: x[1], reverse=True) @@ -222,15 +215,16 @@ def get_node_location(number, nodeInt=1): latitude = node['position']['latitude'] longitude = node['position']['longitude'] except Exception as e: - print (f"{log_timestamp()} System: Error getting location data for {number}") - print (f"System: location data for {number} is {latitude},{longitude}") + logger.error(f"System: Error getting location data for {number}") + logger.debug(f"System: location data for {number} is {latitude},{longitude}") position = [latitude,longitude] return position else: - print (f"{log_timestamp()} System: No location data for {number}") + logger.warning(f"System: No location data for {number} using default location") + #interface1.sendPosition(destinationId=number, wantResponse=True, channelIndex=0) return position else: - print (f"{log_timestamp()} System: No nodes found") + logger.warning(f"System: No nodes found") return position if nodeInt == 2: if interface2.nodes: @@ -241,15 +235,15 @@ def get_node_location(number, nodeInt=1): latitude = node['position']['latitude'] longitude = node['position']['longitude'] except Exception as e: - print (f"{log_timestamp()} System: Error getting location data for {number}") - print (f"System: location data for {number} is {latitude},{longitude}") + logger.error(f"System: Error getting location data for {number}") + logger.info(f"System: location data for {number} is {latitude},{longitude}") position = [latitude,longitude] return position else: - print (f"{log_timestamp()} System: No location data for {number}") + logger.warning(f"System: No location data for {number}") return position else: - print (f"{log_timestamp()} System: No nodes found") + logger.warning(f"System: No nodes found") return position def send_message(message, ch, nodeid=0, nodeInt=1): @@ -257,7 +251,7 @@ def send_message(message, ch, nodeid=0, nodeInt=1): return # if message over MESSAGE_CHUNK_SIZE characters, split it into multiple messages if len(message) > MESSAGE_CHUNK_SIZE: - print (f"{log_timestamp()} System: Splitting Message, Message Length: {len(message)}") + logger.debug(f"System: Splitting Message, Message Length: {len(message)}") # split the message into MESSAGE_CHUNK_SIZE 160 character chunks message = message.replace('\n', ' NEWLINE ') # replace newlines with NEWLINE to keep them in split chunks @@ -283,14 +277,15 @@ def send_message(message, ch, nodeid=0, nodeInt=1): for m in message_list: if nodeid == 0: #Send to channel - print (f"{log_timestamp()} System: Sending Device:{nodeInt} Channel:{ch} Multi-Chunk Message: {m}") + logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + "Sending Multi-Chunk Message: " + CustomFormatter.white + f"{m}") if nodeInt == 1: interface1.sendText(text=m, channelIndex=ch) if nodeInt == 2: interface2.sendText(text=m, channelIndex=ch) else: # Send to DM - print (f"{log_timestamp()} System: Sending DM Device:{nodeInt} Multi-Chunk Message: {m} To: {get_name_from_number(nodeid, 'long', nodeInt)}") + logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending Multi-Chunk Message: " + CustomFormatter.white + f"{m}" + CustomFormatter.yellow +\ + " To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}") if nodeInt == 1: interface1.sendText(text=m, channelIndex=ch, destinationId=nodeid) if nodeInt == 2: @@ -298,14 +293,15 @@ def send_message(message, ch, nodeid=0, nodeInt=1): else: # message is less than MESSAGE_CHUNK_SIZE characters if nodeid == 0: # Send to channel - print (f"{log_timestamp()} System: Sending Device:{nodeInt} Channel:{ch} Message: {message}") + logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + "Sending: " + CustomFormatter.white + f"{message}") if nodeInt == 1: interface1.sendText(text=message, channelIndex=ch) if nodeInt == 2: interface2.sendText(text=message, channelIndex=ch) else: # Send to DM - print (f"{log_timestamp()} System: Sending DM Device:{nodeInt} {message} To: {get_name_from_number(nodeid, 'long', nodeInt)}") + logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending: " + CustomFormatter.white + f"{message}" + CustomFormatter.yellow +\ + " To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}") if nodeInt == 1: interface1.sendText(text=message, channelIndex=ch, destinationId=nodeid) if nodeInt == 2: @@ -339,32 +335,31 @@ def messageTrap(msg): def exit_handler(): # Close the interface and save the BBS messages - print(f"\n{log_timestamp()} System: Closing Autoresponder\n") + logger.debug(f"\nSystem: Closing Autoresponder\n") try: interface1.close() - print(f"{log_timestamp()} System: Interface1 Closed") + logger.debug(f"System: Interface1 Closed") if interface2_enabled: interface2.close() - print(f"{log_timestamp()} System: Interface2 Closed") + logger.debug(f"System: Interface2 Closed") except Exception as e: - print(f"{log_timestamp()} System: Error closing: {e}") + logger.error(f"System: closing: {e}") if bbs_enabled: save_bbsdb() save_bbsdm() - print(f"{log_timestamp()} System: BBS Messages Saved") - print(f"{log_timestamp()} System: Exiting") + logger.debug(f"System: BBS Messages Saved") + logger.debug(f"System: Exiting") asyncLoop.stop() asyncLoop.close() exit (0) - async def handleSignalWatcher(): global lastHamLibAlert, antiSpam, sigWatchBrodcastCh # monitor rigctld for signal strength and frequency while True: msg = await signalWatcher() if msg != ERROR_FETCHING_DATA and msg is not None: - print(f"{log_timestamp()} System: Detected Alert from Hamlib {msg}") + logger.debug(f"System: Detected Alert from Hamlib {msg}") # check we are not spammig the channel limit messages to once per minute if time.time() - lastHamLibAlert > 60: @@ -377,14 +372,14 @@ async def handleSignalWatcher(): if interface2_enabled: send_message(msg, int(ch), 0, 2) else: - print(f"{log_timestamp()} System: antiSpam prevented Alert from Hamlib {msg}") + logger.error(f"System: antiSpam prevented Alert from Hamlib {msg}") else: if antiSpam and sigWatchBrodcastCh != publicChannel: send_message(msg, int(sigWatchBrodcastCh), 0, 1) if interface2_enabled: send_message(msg, int(sigWatchBrodcastCh), 0, 2) else: - print(f"{log_timestamp()} System: antiSpam prevented Alert from Hamlib {msg}") + logger.error(f"System: antiSpam prevented Alert from Hamlib {msg}") await asyncio.sleep(1) pass @@ -400,7 +395,7 @@ async def retry_interface(nodeID=1): try: interface1.close() except Exception as e: - print(f"{log_timestamp()} System: Error closing interface1: {e}") + logger.error(f"System: closing interface1: {e}") if nodeID==2: if interface2 is not None: retry_int2 = True @@ -408,15 +403,15 @@ async def retry_interface(nodeID=1): try: interface2.close() except Exception as e: - print(f"{log_timestamp()} System: Error closing interface2: {e}") + logger.error(f"System: closing interface2: {e}") - print(f"{log_timestamp()} System: Retrying interface in 15 seconds") + logger.debug(f"System: Retrying interface in 15 seconds") if max_retry_count1 == 0: - print(f"{log_timestamp()} System: Max retry count reached for interface1") + logger.critical(f"System: Max retry count reached for interface1") exit_handler() if max_retry_count2 == 0: - print(f"{log_timestamp()} System: Max retry count reached for interface2") + logger.critical(f"System: Max retry count reached for interface2") exit_handler() # wait 15 seconds before retrying await asyncio.sleep(15) @@ -425,32 +420,32 @@ async def retry_interface(nodeID=1): try: if nodeID==1 and retry_int1: interface1 = None - print(f"{log_timestamp()} System: Retrying Interface1") + logger.debug(f"System: Retrying Interface1") if interface1_type == 'serial': interface1 = meshtastic.serial_interface.SerialInterface(port1) elif interface1_type == 'tcp': interface1 = meshtastic.tcp_interface.TCPInterface(hostname1) elif interface1_type == 'ble': interface1 = meshtastic.ble_interface.BLEInterface(mac1) - print(f"{log_timestamp()} System: Interface1 Opened!") + logger.debug(f"System: Interface1 Opened!") retry_int1 = False except Exception as e: - print(f"{log_timestamp()} System: Error opening interface1 on: {e}") + logger.error(f"System: opening interface1 on: {e}") try: if nodeID==2 and retry_int2: interface2 = None - print(f"{log_timestamp()} System: Retrying Interface2") + logger.debug(f"System: Retrying Interface2") if interface2_type == 'serial': interface2 = meshtastic.serial_interface.SerialInterface(port2) elif interface2_type == 'tcp': interface2 = meshtastic.tcp_interface.TCPInterface(hostname2) elif interface2_type == 'ble': interface2 = meshtastic.ble_interface.BLEInterface(mac2) - print(f"{log_timestamp()} System: Interface2 Opened!") + logger.debug(f"System: Interface2 Opened!") retry_int2 = False except Exception as e: - print(f"{log_timestamp()} System: Error opening interface2: {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 @@ -473,21 +468,21 @@ async def watchdog(): # watchdog for connection to the interface while True: await asyncio.sleep(20) - #print(f"{log_timestamp()} System: watchdog running\r", end="") + #print(f"System: watchdog running\r", end="") if interface1 is not None and not retry_int1: try: with suppress_stdout(): interface1.localNode.getMetadata() #if "device_state_version:" not in meta: except Exception as e: - print(f"{log_timestamp()} System: Error communicating with interface1, trying to reconnect: {e}") + logger.error(f"System: communicating with interface1, trying to reconnect: {e}") retry_int1 = True if retry_int1: try: await retry_interface(1) except Exception as e: - print(f"{log_timestamp()} System: Error retrying interface1: {e}") + logger.error(f"System: retrying interface1: {e}") if interface2_enabled: if interface2 is not None and not retry_int2: @@ -495,12 +490,12 @@ async def watchdog(): with suppress_stdout(): interface2.localNode.getMetadata() except Exception as e: - print(f"{log_timestamp()} System: Error communicating with interface2, trying to reconnect: {e}") + logger.error(f"System: communicating with interface2, trying to reconnect: {e}") retry_int2 = True if retry_int2: try: await retry_interface(2) except Exception as e: - print(f"{log_timestamp()} System: Error retrying interface2: {e}") + logger.error(f"System: retrying interface2: {e}") diff --git a/pong_bot.py b/pong_bot.py index b8309ad..9b9adc4 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -5,7 +5,7 @@ import asyncio import time # for sleep, get some when you can :) from pubsub import pub # pip install pubsub -from modules.settings import * +from modules.log import * from modules.system import * def auto_response(message, snr, rssi, hop, message_from_id, channel_number, deviceID): @@ -134,7 +134,7 @@ def onReceive(packet, interface): if message_string == help_message or message_string == welcome_message or "CMD?:" in message_string: # ignore help and welcome messages - print(f"{log_timestamp()} Got Own Welcome/Help header. Device:{rxNode} From: {get_name_from_number(message_from_id)}") + logger.warning(f"Got Own Welcome/Help header. From: {get_name_from_number(message_from_id, 'long', rxNode)}") return # If the packet is a DM (Direct Message) respond to it, otherwise validate its a message for us on the channel @@ -143,17 +143,21 @@ def onReceive(packet, interface): # check if the message contains a trap word, DMs are always responded to if messageTrap(message_string): - print(f"{log_timestamp()} Received DM: {message_string} on Device:{rxNode} Channel: {channel_number} From: {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") # respond with DM send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) else: # respond with welcome message on DM - print(f"{log_timestamp()} Ignoring DM: {message_string} on Device:{rxNode} From: {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.warning(f"Device:{rxNode} Ignoring DM: {message_string} From: {get_name_from_number(message_from_id, 'long', rxNode)}") send_message(welcome_message, channel_number, message_from_id, rxNode) + msgLogger.info(f"{get_name_from_number(message_from_id, 'long', rxNode)} | {message_string}") else: # message is on a channel if messageTrap(message_string): - print(f"{log_timestamp()} Received On Device:{rxNode} Channel {channel_number}: {message_string} From: {get_name_from_number(message_from_id, 'long', rxNode)}") + # message is for bot to respond to + logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") if useDMForResponse: # respond to channel message via direct message send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) @@ -161,7 +165,7 @@ def onReceive(packet, interface): # or respond to channel message on the channel itself if channel_number == publicChannel: # warning user spamming default channel - print(f"{log_timestamp()} System: Warning spamming default channel not allowed. sending DM to {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.error(f"System: Warning spamming default channel not allowed. sending DM to {get_name_from_number(message_from_id, 'long', rxNode)}") # respond to channel message via direct message send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) @@ -169,8 +173,8 @@ def onReceive(packet, interface): # respond to channel message on the channel itself 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 - # add the message to the message history but limit if zuluTime: timestamp = datetime.now().strftime("%Y-%m-%d %H:%M:%S") else: @@ -191,30 +195,38 @@ def onReceive(packet, interface): time.sleep(0.7) if str(channel_number) in repeater_channels: if rxNode == 1: - print(f"{log_timestamp()} Repeating message on Device2 Channel:{channel_number}") + logger.debug(f"Repeating message on Device2 Channel:{channel_number}") send_message(rMsg, channel_number, 0, 2) elif rxNode == 2: - print(f"{log_timestamp()} Repeating message on Device1 Channel:{channel_number}") + logger.debug(f"Repeating message on Device1 Channel:{channel_number}") send_message(rMsg, channel_number, 0, 1) else: - print(f"{log_timestamp()} System: Ignoring incoming Device:{rxNode} Channel:{channel_number} Message: {message_string} From: {get_name_from_number(message_from_id)}") + # nothing to do for us + logger.info(f"Ignoring incoming Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Message:" + CustomFormatter.white +\ + f" {message_string} " + CustomFormatter.yellow + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}") + msgLogger.info(f"{get_name_from_number(message_from_id, 'long', rxNode)} | {message_string}") except KeyError as e: - print(f"{log_timestamp()} System: Error processing packet: {e} Device:{rxNode}") + logger.critical(f"System: Error processing packet: {e} Device:{rxNode}") print(packet) # print the packet for debugging print("END of packet \n") 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') - - msg = (f"{log_timestamp()} 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)}") - print (msg) + 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: - msg = (f"{log_timestamp()} System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)}," - f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}") - print (msg) + logger.info(f"System: Autoresponder Started for Device2 {get_name_from_number(myNodeNum2, 'long', 2)}," + f"{get_name_from_number(myNodeNum2, 'short', 2)}. NodeID: {myNodeNum2}, {decimal_to_hex(myNodeNum2)}") + if store_forward_enabled: + logger.debug(f"System: Store and Forward Enabled using limit: {storeFlimit}") + if useDMForResponse: + logger.debug(f"System: Respond by DM only") + if repeater_enabled and interface2_enabled: + 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'))}") # here we go loopty loo while True: From 2e11d5a4fc18af47e2c9401860d28c02da3ee409 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 6 Aug 2024 13:27:42 -0700 Subject: [PATCH 2/5] Update log.py --- modules/log.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/modules/log.py b/modules/log.py index ff1bb7d..d6768fd 100644 --- a/modules/log.py +++ b/modules/log.py @@ -45,10 +45,12 @@ msgLogFormat = '%(asctime)s | %(message)s' # Create stdout handler for logging to the console stdout_handler = logging.StreamHandler() +# Set level for stdout handler (logs DEBUG level and above) stdout_handler.setLevel(logging.DEBUG) +# Set format for stdout handler stdout_handler.setFormatter(CustomFormatter(logFormat)) -# Create file handler for logging to a file (logs INFO level and above) +# 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) From 0b71ec18a918f4447ae796fe0bcf1c96ac94d4d5 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 6 Aug 2024 13:38:45 -0700 Subject: [PATCH 3/5] uwcolors i know of corruption at uw --- mesh_bot.py | 6 +++--- modules/system.py | 6 +++--- pong_bot.py | 6 +++--- 3 files changed, 9 insertions(+), 9 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index d9fc5ee..ce9ed0f 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -257,7 +257,7 @@ def onReceive(packet, interface): # message is DM to us # check if the message contains a trap word, DMs are always responded to if messageTrap(message_string): - logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") # respond with DM send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) @@ -270,7 +270,7 @@ def onReceive(packet, interface): # message is on a channel if messageTrap(message_string): # message is for bot to respond to - logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") if useDMForResponse: # respond to channel message via direct message @@ -318,7 +318,7 @@ def onReceive(packet, interface): else: # nothing to do for us logger.info(f"Ignoring incoming Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Message:" + CustomFormatter.white +\ - f" {message_string} " + CustomFormatter.yellow + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}") + f" {message_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}") msgLogger.info(f"{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}") diff --git a/modules/system.py b/modules/system.py index 05778a0..b2406c2 100644 --- a/modules/system.py +++ b/modules/system.py @@ -284,7 +284,7 @@ def send_message(message, ch, nodeid=0, nodeInt=1): interface2.sendText(text=m, channelIndex=ch) else: # Send to DM - logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending Multi-Chunk Message: " + CustomFormatter.white + f"{m}" + CustomFormatter.yellow +\ + logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending Multi-Chunk Message: " + CustomFormatter.white + f"{m}" + CustomFormatter.purple +\ " To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}") if nodeInt == 1: interface1.sendText(text=m, channelIndex=ch, destinationId=nodeid) @@ -300,7 +300,7 @@ def send_message(message, ch, nodeid=0, nodeInt=1): interface2.sendText(text=message, channelIndex=ch) else: # Send to DM - logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending: " + CustomFormatter.white + f"{message}" + CustomFormatter.yellow +\ + logger.info(f"Device:{nodeInt} " + CustomFormatter.red + "Sending: " + CustomFormatter.white + f"{message}" + CustomFormatter.purple +\ " To: " + CustomFormatter.white + f"{get_name_from_number(nodeid, 'long', nodeInt)}") if nodeInt == 1: interface1.sendText(text=message, channelIndex=ch, destinationId=nodeid) @@ -468,7 +468,7 @@ async def watchdog(): # watchdog for connection to the interface while True: await asyncio.sleep(20) - #print(f"System: watchdog running\r", end="") + #print(f"MeshBot System: watchdog running\r", end="") if interface1 is not None and not retry_int1: try: with suppress_stdout(): diff --git a/pong_bot.py b/pong_bot.py index 9b9adc4..6ce1dba 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -143,7 +143,7 @@ def onReceive(packet, interface): # check if the message contains a trap word, DMs are always responded to if messageTrap(message_string): - logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") # respond with DM send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) @@ -156,7 +156,7 @@ def onReceive(packet, interface): # message is on a channel if messageTrap(message_string): # message is for bot to respond to - logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.yellow +\ + logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") if useDMForResponse: # respond to channel message via direct message @@ -203,7 +203,7 @@ def onReceive(packet, interface): else: # nothing to do for us logger.info(f"Ignoring incoming Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Message:" + CustomFormatter.white +\ - f" {message_string} " + CustomFormatter.yellow + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}") + f" {message_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}") msgLogger.info(f"{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}") From dd3cc524ff62dee78d16754fa084dd77b5d16d59 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 6 Aug 2024 13:49:51 -0700 Subject: [PATCH 4/5] enhance --- mesh_bot.py | 2 +- pong_bot.py | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index ce9ed0f..85be70d 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -279,7 +279,7 @@ def onReceive(packet, interface): # or respond to channel message on the channel itself if channel_number == publicChannel and antiSpam: # warning user spamming default channel - logger.error(f"System: Warning spamming default channel not allowed. sending DM to {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.error(f"System: AntiSpam protection, sending DM to: {get_name_from_number(message_from_id, 'long', rxNode)}") # respond to channel message via direct message send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) diff --git a/pong_bot.py b/pong_bot.py index 6ce1dba..09b4526 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -163,9 +163,9 @@ def onReceive(packet, interface): send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) else: # or respond to channel message on the channel itself - if channel_number == publicChannel: + if channel_number == publicChannel and antiSpam: # warning user spamming default channel - logger.error(f"System: Warning spamming default channel not allowed. sending DM to {get_name_from_number(message_from_id, 'long', rxNode)}") + logger.error(f"System: AntiSpam protection, sending DM to: {get_name_from_number(message_from_id, 'long', rxNode)}") # respond to channel message via direct message send_message(auto_response(message_string, snr, rssi, hop, message_from_id, channel_number, rxNode), channel_number, message_from_id, rxNode) From 2467b2f984b89a4e41bd7e4f6adf43beee2e13d5 Mon Sep 17 00:00:00 2001 From: SpudGunMan Date: Tue, 6 Aug 2024 14:26:17 -0700 Subject: [PATCH 5/5] typo --- mesh_bot.py | 2 +- pong_bot.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/mesh_bot.py b/mesh_bot.py index 85be70d..edbbd30 100755 --- a/mesh_bot.py +++ b/mesh_bot.py @@ -270,7 +270,7 @@ def onReceive(packet, interface): # message is on a channel if messageTrap(message_string): # message is for bot to respond to - logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ + logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") if useDMForResponse: # respond to channel message via direct message diff --git a/pong_bot.py b/pong_bot.py index 09b4526..62702f0 100755 --- a/pong_bot.py +++ b/pong_bot.py @@ -156,7 +156,7 @@ def onReceive(packet, interface): # message is on a channel if messageTrap(message_string): # message is for bot to respond to - logger.info(f"Device:{rxNode} Channel {channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ + logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Received: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\ "From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}") if useDMForResponse: # respond to channel message via direct message