mirror of
https://github.com/SpudGunMan/meshing-around.git
synced 2026-05-09 14:54:29 +02:00
Compare commits
10 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 8a0eb62574 | |||
| 873509b3cc | |||
| b03c5c9c2e | |||
| 2ce976ca8a | |||
| 76a5913e08 | |||
| 3819791fcd | |||
| 9fe580a3cb | |||
| 8567c3ad84 | |||
| f68f7f10ca | |||
| a02025d4a0 |
@@ -44,7 +44,7 @@ jobs:
|
||||
# It uses the `tags` and `labels` parameters to tag and label the image with the output from the "meta" step.
|
||||
- name: Build and push Docker image
|
||||
id: push
|
||||
uses: docker/build-push-action@d08e5c354a6adb9ed34480a06d141179aa583294
|
||||
uses: docker/build-push-action@bcafcacb16a39f128d818304e6c9c0c18556b85f
|
||||
with:
|
||||
context: .
|
||||
push: true
|
||||
|
||||
+2
-2
@@ -326,9 +326,9 @@ schedulerMotd = False
|
||||
# 'tide' (time/day), 'solar' (time/day) for automated information broadcasts, matching module needs enabled!
|
||||
# 'custom' for module/scheduler.py custom schedule examples
|
||||
value =
|
||||
# interval to use when time is not set (e.g. every 2 days)
|
||||
# interval for recurring schedules (e.g. every 2 days, or every 2 days at a set time)
|
||||
interval =
|
||||
# time of day in 24:00 hour format when value is 'day' and interval is not set
|
||||
# time of day in 24:00 hour format when value is 'day' (optional with interval)
|
||||
# Process run :00,:20,:40 try and vary the 20 minute offsets to avoid collision
|
||||
time =
|
||||
|
||||
|
||||
@@ -19,7 +19,7 @@ fi
|
||||
|
||||
export REQUESTS_CA_BUNDLE=/etc/ssl/certs/ca-certificates.crt
|
||||
export SSL_CERT_FILE=/etc/ssl/certs/ca-certificates.crt
|
||||
|
||||
export HOME=$(pwd)
|
||||
# launch the application
|
||||
if [[ "$1" == pong* ]]; then
|
||||
python3 pong_bot.py
|
||||
|
||||
+68
-34
@@ -591,6 +591,11 @@ llmRunCounter = 0
|
||||
llmTotalRuntime = []
|
||||
llmLocationTable = [{'nodeID': 1234567890, 'location': 'No Location'},]
|
||||
|
||||
# Runtime safety caps to avoid unbounded growth on long-lived systems.
|
||||
MAX_SEEN_NODES = 5000
|
||||
MAX_LLM_LOCATION_ENTRIES = 50
|
||||
MAX_LLM_RUNTIME_SAMPLES = 50
|
||||
|
||||
def handle_satpass(message_from_id, deviceID, message='', vox=False):
|
||||
if vox:
|
||||
location = (my_settings.latitudeValue, my_settings.longitudeValue)
|
||||
@@ -656,6 +661,8 @@ def handle_llm(message_from_id, channel_number, deviceID, message, publicChannel
|
||||
# likely a DM
|
||||
user_input = message
|
||||
# consider this a command use for the cmdHistory list
|
||||
if len(cmdHistory) > 50:
|
||||
cmdHistory.pop(0)
|
||||
cmdHistory.append({'nodeID': message_from_id, 'cmd': 'llm-use', 'time': time.time()})
|
||||
|
||||
# check for a welcome message (is this redundant?)
|
||||
@@ -679,6 +686,8 @@ def handle_llm(message_from_id, channel_number, deviceID, message, publicChannel
|
||||
# if not in table add the location
|
||||
if not any(d['nodeID'] == message_from_id for d in llmLocationTable):
|
||||
llmLocationTable.append({'nodeID': message_from_id, 'location': location_name})
|
||||
if len(llmLocationTable) > MAX_LLM_LOCATION_ENTRIES:
|
||||
llmLocationTable = llmLocationTable[-MAX_LLM_LOCATION_ENTRIES:]
|
||||
|
||||
user_input = user_input.strip()
|
||||
|
||||
@@ -709,6 +718,8 @@ def handle_llm(message_from_id, channel_number, deviceID, message, publicChannel
|
||||
end = time.time()
|
||||
llmRunCounter += 1
|
||||
llmTotalRuntime.append(end - start)
|
||||
if len(llmTotalRuntime) > MAX_LLM_RUNTIME_SAMPLES:
|
||||
llmTotalRuntime = llmTotalRuntime[-MAX_LLM_RUNTIME_SAMPLES:]
|
||||
|
||||
return response
|
||||
|
||||
@@ -1854,14 +1865,23 @@ def onReceive(packet, interface):
|
||||
# Priocess the incoming packet, handles the responses to the packet with auto_response()
|
||||
# Sends the packet to the correct handler for processing
|
||||
|
||||
if not isinstance(packet, dict):
|
||||
logger.warning(f"System: Ignoring malformed packet type: {type(packet).__name__}")
|
||||
return
|
||||
|
||||
decoded = packet.get('decoded')
|
||||
if not isinstance(decoded, dict):
|
||||
decoded = {}
|
||||
|
||||
# extract interface details from inbound packet
|
||||
rxType = type(interface).__name__
|
||||
|
||||
# Values assinged to the packet
|
||||
packet_id = None
|
||||
rxNode = message_from_id = snr = rssi = hop = hop_away = channel_number = hop_start = hop_count = hop_limit = 0
|
||||
pkiStatus = (False, 'ABC')
|
||||
rxNodeHostName = None
|
||||
replyIDset = False
|
||||
replyIDset = None
|
||||
emojiSeen = False
|
||||
simulator_flag = False
|
||||
isDM = False
|
||||
@@ -1899,8 +1919,8 @@ def onReceive(packet, interface):
|
||||
|
||||
if rxNode is None:
|
||||
# default to interface 1 ## FIXME needs better like a default interface setting or hash lookup
|
||||
if 'decoded' in packet and packet['decoded']['portnum'] in ['ADMIN_APP', 'SIMULATOR_APP']:
|
||||
session_passkey = packet.get('decoded', {}).get('admin', {}).get('sessionPasskey', None)
|
||||
if decoded.get('portnum') in ['ADMIN_APP', 'SIMULATOR_APP']:
|
||||
session_passkey = decoded.get('admin', {}).get('sessionPasskey', None)
|
||||
rxNode = 1
|
||||
|
||||
# check if the packet has a channel flag use it ## FIXME needs to be channel hash lookup
|
||||
@@ -1940,17 +1960,22 @@ def onReceive(packet, interface):
|
||||
# logger.debug(f"System: Received Packet on Channel:{channel_number} Name:{channel_name} on Interface:{rxNode}")
|
||||
|
||||
# check if the packet has a simulator flag
|
||||
simulator_flag = packet.get('decoded', {}).get('simulator', False)
|
||||
simulator_flag = decoded.get('simulator', False)
|
||||
if isinstance(simulator_flag, dict):
|
||||
# assume Software Simulator
|
||||
simulator_flag = True
|
||||
|
||||
# set the message_from_id
|
||||
message_from_id = packet['from']
|
||||
message_from_id = packet.get('from')
|
||||
if message_from_id is None:
|
||||
logger.warning(f"System: Ignoring packet missing 'from' field on Device:{rxNode}")
|
||||
return
|
||||
|
||||
# if message_from_id is not in the seenNodes list add it
|
||||
if not any(node.get('nodeID') == message_from_id for node in seenNodes):
|
||||
seenNodes.append({'nodeID': message_from_id, 'rxInterface': rxNode, 'channel': channel_number, 'welcome': False, 'first_seen': time.time(), 'lastSeen': time.time()})
|
||||
if len(seenNodes) > MAX_SEEN_NODES:
|
||||
seenNodes = seenNodes[-MAX_SEEN_NODES:]
|
||||
else:
|
||||
# update lastSeen time
|
||||
for node in seenNodes:
|
||||
@@ -1958,7 +1983,7 @@ def onReceive(packet, interface):
|
||||
node['lastSeen'] = time.time()
|
||||
break
|
||||
# BBS DM MAIL CHECKER
|
||||
if bbs_enabled and 'decoded' in packet:
|
||||
if bbs_enabled and decoded:
|
||||
msg = bbs_check_dm(message_from_id)
|
||||
if msg:
|
||||
logger.info(f"System: BBS DM Delivery: {msg[1]} For: {get_name_from_number(message_from_id, 'long', rxNode)}")
|
||||
@@ -1973,18 +1998,25 @@ def onReceive(packet, interface):
|
||||
|
||||
# handle TEXT_MESSAGE_APP
|
||||
try:
|
||||
if 'decoded' in packet and packet['decoded']['portnum'] == 'TEXT_MESSAGE_APP':
|
||||
message_bytes = packet['decoded']['payload']
|
||||
message_string = message_bytes.decode('utf-8')
|
||||
via_mqtt = packet['decoded'].get('viaMqtt', False)
|
||||
if decoded.get('portnum') == 'TEXT_MESSAGE_APP':
|
||||
message_bytes = decoded.get('payload', b'')
|
||||
if isinstance(message_bytes, bytes):
|
||||
message_string = message_bytes.decode('utf-8', errors='replace')
|
||||
elif isinstance(message_bytes, str):
|
||||
message_string = message_bytes
|
||||
else:
|
||||
logger.warning(f"System: Ignoring TEXT_MESSAGE_APP with invalid payload type: {type(message_bytes).__name__}")
|
||||
return
|
||||
message_log_string = message_string.replace('\r', ' ').replace('\n', ' ')
|
||||
via_mqtt = decoded.get('viaMqtt', False)
|
||||
transport_mechanism = (
|
||||
packet.get('transport_mechanism')
|
||||
or packet.get('transportMechanism')
|
||||
or (packet.get('decoded', {}).get('transport_mechanism'))
|
||||
or (packet.get('decoded', {}).get('transportMechanism'))
|
||||
or decoded.get('transport_mechanism')
|
||||
or decoded.get('transportMechanism')
|
||||
or 'unknown'
|
||||
)
|
||||
rx_time = packet['decoded'].get('rxTime', time.time())
|
||||
rx_time = decoded.get('rxTime', time.time())
|
||||
|
||||
# check if the packet is from us
|
||||
if message_from_id in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]:
|
||||
@@ -1999,9 +2031,11 @@ def onReceive(packet, interface):
|
||||
if packet.get('publicKey'):
|
||||
pkiStatus = packet.get('pkiEncrypted', False), packet.get('publicKey', 'ABC')
|
||||
|
||||
# check if the packet has replyId flag // currently unused in the code
|
||||
if packet.get('replyId'):
|
||||
replyIDset = packet.get('replyId', False)
|
||||
# Use packet id for threaded replies;
|
||||
packet_id = packet.get('id', None)
|
||||
|
||||
# existing reply - unused for tracking
|
||||
replyIDSet = packet.get('replyIDSet', None)
|
||||
|
||||
# check if the packet has emoji flag set it // currently unused in the code
|
||||
if packet.get('emoji'):
|
||||
@@ -2066,13 +2100,13 @@ def onReceive(packet, interface):
|
||||
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'] in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]:
|
||||
if packet.get('to') in [myNodeNum1, myNodeNum2, myNodeNum3, myNodeNum4, myNodeNum5, myNodeNum6, myNodeNum7, myNodeNum8, myNodeNum9]:
|
||||
# message is DM to us
|
||||
isDM = True
|
||||
# check if the message contains a trap word, DMs are always responded to
|
||||
if (messageTrap(message_string) and not llm_enabled) or messageTrap(message_string.split()[0]):
|
||||
# log the message to stdout
|
||||
logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\
|
||||
logger.info(f"Device:{rxNode} Channel: {channel_number} " + CustomFormatter.green + f"Received DM: " + CustomFormatter.white + f"{message_log_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, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, message_from_id, rxNode)
|
||||
@@ -2082,7 +2116,7 @@ def onReceive(packet, interface):
|
||||
playingGame = checkPlayingGame(message_from_id, message_string, rxNode, channel_number)
|
||||
elif hop_count >= my_settings.game_hop_limit:
|
||||
if games_enabled:
|
||||
logger.warning(f"Device:{rxNode} Ignoring Request to Play Game: {message_string} From: {get_name_from_number(message_from_id, 'long', rxNode)} with hop count: {hop}")
|
||||
logger.warning(f"Device:{rxNode} Ignoring Request to Play Game: {message_log_string} From: {get_name_from_number(message_from_id, 'long', rxNode)} with hop count: {hop}")
|
||||
send_message(f"Your hop count exceeds safe playable distance at {hop_count} hops", channel_number, message_from_id, rxNode)
|
||||
else:
|
||||
playingGame = False
|
||||
@@ -2096,7 +2130,7 @@ def onReceive(packet, interface):
|
||||
send_message(llm, channel_number, message_from_id, rxNode)
|
||||
else:
|
||||
# respond with welcome message on DM
|
||||
logger.warning(f"Device:{rxNode} Ignoring DM: {message_string} From: {get_name_from_number(message_from_id, 'long', rxNode)}")
|
||||
logger.warning(f"Device:{rxNode} Ignoring DM: {message_log_string} From: {get_name_from_number(message_from_id, 'long', rxNode)}")
|
||||
|
||||
# if seenNodes list is not marked as welcomed send welcome message
|
||||
if not any(node['nodeID'] == message_from_id and node['welcome'] == True for node in seenNodes):
|
||||
@@ -2122,26 +2156,26 @@ def onReceive(packet, interface):
|
||||
|
||||
# log the message to the message log
|
||||
if log_messages_to_file:
|
||||
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | DM | " + message_string.replace('\n', '-nl-'))
|
||||
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | DM | " + message_log_string)
|
||||
else:
|
||||
# message is on a channel
|
||||
if messageTrap(message_string):
|
||||
# message is for us to respond to, or is it...
|
||||
if my_settings.ignoreDefaultChannel and channel_number == my_settings.publicChannel:
|
||||
logger.debug(f"System: Ignoring CMD:{message_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Default Channel:{channel_number}")
|
||||
logger.debug(f"System: Ignoring CMD:{message_log_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Default Channel:{channel_number}")
|
||||
elif str(message_from_id) in my_settings.bbs_ban_list:
|
||||
logger.debug(f"System: Ignoring CMD:{message_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Cantankerous Node")
|
||||
logger.debug(f"System: Ignoring CMD:{message_log_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Cantankerous Node")
|
||||
elif str(channel_number) in my_settings.ignoreChannels:
|
||||
logger.debug(f"System: Ignoring CMD:{message_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Ignored Channel:{channel_number}")
|
||||
logger.debug(f"System: Ignoring CMD:{message_log_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Ignored Channel:{channel_number}")
|
||||
elif my_settings.cmdBang and not message_string.startswith("!"):
|
||||
logger.debug(f"System: Ignoring CMD:{message_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Didnt sound like they meant it")
|
||||
logger.debug(f"System: Ignoring CMD:{message_log_string} From: {get_name_from_number(message_from_id, 'short', rxNode)} Didnt sound like they meant it")
|
||||
else:
|
||||
# message is for bot to respond to, seriously this time..
|
||||
logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "ReceivedChannel: " + CustomFormatter.white + f"{message_string} " + CustomFormatter.purple +\
|
||||
logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "ReceivedChannel: " + CustomFormatter.white + f"{message_log_string} " + CustomFormatter.purple +\
|
||||
"From: " + CustomFormatter.white + f"{get_name_from_number(message_from_id, 'long', rxNode)}")
|
||||
if my_settings.useDMForResponse:
|
||||
# respond to channel message via direct message
|
||||
send_message(auto_response(message_string, snr, rssi, hop, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, message_from_id, rxNode)
|
||||
send_message(auto_response(message_string, snr, rssi, hop, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, message_from_id, rxNode, reply_id=packet_id)
|
||||
else:
|
||||
# or respond to channel message on the channel itself
|
||||
if channel_number == my_settings.publicChannel and my_settings.antiSpam:
|
||||
@@ -2149,10 +2183,10 @@ def onReceive(packet, interface):
|
||||
logger.warning(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, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, message_from_id, rxNode)
|
||||
send_message(auto_response(message_string, snr, rssi, hop, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, message_from_id, rxNode, reply_id=packet_id)
|
||||
else:
|
||||
# respond to channel message on the channel itself
|
||||
send_message(auto_response(message_string, snr, rssi, hop, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, 0, rxNode)
|
||||
send_message(auto_response(message_string, snr, rssi, hop, pkiStatus, message_from_id, channel_number, rxNode, isDM), channel_number, 0, rxNode, reply_id=packet_id)
|
||||
|
||||
else:
|
||||
# message is not for us to respond to
|
||||
@@ -2172,9 +2206,9 @@ def onReceive(packet, interface):
|
||||
|
||||
# print the message to the log and sdout
|
||||
logger.info(f"Device:{rxNode} Channel:{channel_number} " + CustomFormatter.green + "Ignoring Message:" + CustomFormatter.white +\
|
||||
f" {message_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}")
|
||||
f" {message_log_string} " + CustomFormatter.purple + "From:" + CustomFormatter.white + f" {get_name_from_number(message_from_id)}")
|
||||
if my_settings.log_messages_to_file:
|
||||
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_string.replace('\n', '-nl-'))
|
||||
msgLogger.info(f"Device:{rxNode} Channel:{channel_number} | {get_name_from_number(message_from_id, 'long', rxNode)} | " + message_log_string)
|
||||
|
||||
# repeat the message on the other device
|
||||
if my_settings.repeater_enabled and my_settings.multiple_interface:
|
||||
@@ -2204,7 +2238,7 @@ def onReceive(packet, interface):
|
||||
hello(message_from_id, name)
|
||||
# send a hello message as a DM
|
||||
if not my_settings.train_qrz:
|
||||
send_message(f"Hello {name} {qrz_hello_string}", channel_number, message_from_id, rxNode)
|
||||
send_message(f"Hello {name} {qrz_hello_string}", channel_number, message_from_id, rxNode, reply_id=packet_id)
|
||||
|
||||
# handle mini games
|
||||
if my_settings.wordOfTheDay:
|
||||
@@ -2232,8 +2266,8 @@ def onReceive(packet, interface):
|
||||
else:
|
||||
# Evaluate non TEXT_MESSAGE_APP packets
|
||||
consumeMetadata(packet, rxNode, channel_number)
|
||||
except KeyError as e:
|
||||
logger.critical(f"System: Error processing packet: {e} Device:{rxNode}")
|
||||
except Exception as e:
|
||||
logger.exception(f"System: Error processing packet: {e} Device:{rxNode}")
|
||||
logger.debug(f"System: Error Packet = {packet}")
|
||||
|
||||
async def start_rx():
|
||||
|
||||
+13
-4
@@ -104,14 +104,23 @@ def setup_scheduler(
|
||||
|
||||
# Basic Scheduler Options
|
||||
basicOptions = ['day', 'mon', 'tue', 'wed', 'thu', 'fri', 'sat', 'sun', 'hour', 'min']
|
||||
effective_interval = schedulerIntervalInt
|
||||
if any(option in schedulerValue for option in basicOptions):
|
||||
if schedulerValue == 'day':
|
||||
day_interval = safe_int(schedulerInterval, 1, type="interval")
|
||||
if day_interval < 1:
|
||||
logger.debug(f"System: Scheduler config interval '{schedulerInterval}' invalid for day schedule, using default 1")
|
||||
day_interval = 1
|
||||
effective_interval = day_interval
|
||||
if schedulerTime:
|
||||
# Specific time each day
|
||||
schedule.every().day.at(schedulerTime).do(send_sched_msg)
|
||||
# Specific time at a daily or multi-day interval
|
||||
if day_interval == 1:
|
||||
schedule.every().day.at(schedulerTime).do(send_sched_msg)
|
||||
else:
|
||||
schedule.every(day_interval).days.at(schedulerTime).do(send_sched_msg)
|
||||
else:
|
||||
# Every N days
|
||||
schedule.every(schedulerIntervalInt).days.do(send_sched_msg)
|
||||
schedule.every(day_interval).days.do(send_sched_msg)
|
||||
elif 'mon' in schedulerValue and schedulerTime:
|
||||
schedule.every().monday.at(schedulerTime).do(send_sched_msg)
|
||||
elif 'tue' in schedulerValue and schedulerTime:
|
||||
@@ -130,7 +139,7 @@ def setup_scheduler(
|
||||
schedule.every(schedulerIntervalInt).hours.do(send_sched_msg)
|
||||
elif 'min' in schedulerValue:
|
||||
schedule.every(schedulerIntervalInt).minutes.do(send_sched_msg)
|
||||
logger.debug(f"System: Starting the basic scheduler to send '{scheduler_message}' on schedule '{schedulerValue}' every {schedulerIntervalInt} interval at time '{schedulerTime}' on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
logger.debug(f"System: Starting the basic scheduler to send '{scheduler_message}' on schedule '{schedulerValue}' every {effective_interval} interval at time '{schedulerTime}' on Device:{schedulerInterface} Channel:{schedulerChannel}")
|
||||
elif 'joke' in schedulerValue:
|
||||
schedule.every(schedulerIntervalInt).minutes.do(
|
||||
lambda: send_message(tell_joke(), schedulerChannel, 0, schedulerInterface)
|
||||
|
||||
+7
-1
@@ -37,6 +37,12 @@ def hf_band_conditions():
|
||||
def solar_conditions():
|
||||
# radio related solar conditions from hamsql.com
|
||||
solar_cond = ""
|
||||
solar_a_index = ""
|
||||
solar_k_index = ""
|
||||
solar_xray = ""
|
||||
solar_flux = ""
|
||||
sunspots = ""
|
||||
signalnoise = ""
|
||||
try:
|
||||
solar_cond = requests.get("https://www.hamqsl.com/solarxml.php", timeout=urlTimeoutSeconds)
|
||||
if solar_cond.ok:
|
||||
@@ -52,7 +58,7 @@ def solar_conditions():
|
||||
solar_flux = i.getElementsByTagName("solarflux")[0].childNodes[0].data
|
||||
sunspots = i.getElementsByTagName("sunspots")[0].childNodes[0].data
|
||||
signalnoise = i.getElementsByTagName("signalnoise")[0].childNodes[0].data
|
||||
solar_cond = "A-Index: " + solar_a_index + "\nK-Index: " + solar_k_index + "\nSunspots: " + sunspots + "\nX-Ray Flux: " + solar_xray + "\nSolar Flux: " + solar_flux + "\nSignal Noise: " + signalnoise
|
||||
solar_cond = "A: " + solar_a_index + "\nK: " + solar_k_index + "\nSunspots: " + sunspots + "\nX-Ray Flux: " + solar_xray + "\nSolar Flux: " + solar_flux + "\nNoise: " + signalnoise
|
||||
else:
|
||||
logger.error("Solar: Error fetching solar conditions")
|
||||
solar_cond = ERROR_FETCHING_DATA
|
||||
|
||||
+47
-18
@@ -852,7 +852,7 @@ def messageChunker(message):
|
||||
except Exception as e:
|
||||
logger.warning(f"System: Exception during message chunking: {e} (message length: {len(message)})")
|
||||
|
||||
def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False):
|
||||
def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False, reply_id=None):
|
||||
# Send a message to a channel or DM
|
||||
interface = globals()[f'interface{nodeInt}']
|
||||
# Check if the message is empty
|
||||
@@ -860,6 +860,28 @@ def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False):
|
||||
return False
|
||||
|
||||
try:
|
||||
def _send_with_reply(**kwargs):
|
||||
# For threaded replies, send as DATA payload to match Meshtastic inline-reply behavior. no API call today.
|
||||
if reply_id is not None:
|
||||
text_payload = kwargs.pop('text', '')
|
||||
if isinstance(text_payload, str):
|
||||
raw_payload = text_payload.encode('utf-8')
|
||||
else:
|
||||
raw_payload = text_payload
|
||||
|
||||
data_kwargs = {
|
||||
# 1 == TEXT_MESSAGE_APP, required so clients render payload as chat text.
|
||||
'portNum': 1,
|
||||
'channelIndex': kwargs.get('channelIndex', ch),
|
||||
'wantAck': kwargs.get('wantAck', wantAck),
|
||||
}
|
||||
if kwargs.get('destinationId'):
|
||||
data_kwargs['destinationId'] = kwargs.get('destinationId')
|
||||
# send the data payload with the replyId for threading
|
||||
return interface.sendData(raw_payload, replyId=reply_id, **data_kwargs)
|
||||
# Otherwise, send as normal text message
|
||||
return interface.sendText(**kwargs)
|
||||
|
||||
# Force chunking and log if message exceeds maxBuffer
|
||||
if len(message.encode('utf-8')) > maxBuffer:
|
||||
logger.debug(f"System: Message length {len(message.encode('utf-8'))} exceeds maxBuffer{maxBuffer}, forcing chunking.")
|
||||
@@ -880,20 +902,20 @@ def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False):
|
||||
# Send to channel
|
||||
if wantAck:
|
||||
logger.info(f"Device:{nodeInt} Channel:{ch} " + CustomFormatter.red + f"req.ACK " + f"Chunker{chunkOf} SendingChannel: " + CustomFormatter.white + m.replace('\n', ' '))
|
||||
interface.sendText(text=m, channelIndex=ch, wantAck=True)
|
||||
_send_with_reply(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)
|
||||
_send_with_reply(text=m, channelIndex=ch)
|
||||
else:
|
||||
# Send to DM
|
||||
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, wantAck=True)
|
||||
_send_with_reply(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)
|
||||
_send_with_reply(text=m, channelIndex=ch, destinationId=nodeid)
|
||||
|
||||
# Throttle the message sending to prevent spamming the device
|
||||
if (message_list.index(m)+1) % 4 == 0:
|
||||
@@ -908,20 +930,20 @@ def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False):
|
||||
# Send to channel
|
||||
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)
|
||||
_send_with_reply(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)
|
||||
_send_with_reply(text=message, channelIndex=ch)
|
||||
else:
|
||||
# Send to DM
|
||||
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)
|
||||
_send_with_reply(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)
|
||||
_send_with_reply(text=message, channelIndex=ch, destinationId=nodeid)
|
||||
# Throttle the message sending to prevent spamming the device
|
||||
time.sleep(responseDelay)
|
||||
return True
|
||||
@@ -929,17 +951,24 @@ def send_message(message, ch, nodeid=0, nodeInt=1, bypassChuncking=False):
|
||||
logger.error(f"System: Exception during send_message: {e} (message length: {len(message)})")
|
||||
return False
|
||||
|
||||
def send_raw_bytes(nodeid, raw_bytes, nodeInt=1, channel=0, portnum=256, want_ack=True):
|
||||
def send_raw_bytes(nodeid, raw_bytes, nodeInt=1, channel=0, portnum=256, want_ack=True, reply_id=None):
|
||||
# Send raw bytes to a node using the Meshtastic interface.
|
||||
interface = globals()[f'interface{nodeInt}']
|
||||
try:
|
||||
interface.sendData(
|
||||
raw_bytes,
|
||||
destinationId=nodeid,
|
||||
portNum=portnum,
|
||||
channelIndex=channel,
|
||||
wantAck=want_ack
|
||||
)
|
||||
send_kwargs = {
|
||||
'destinationId': nodeid,
|
||||
'portNum': portnum,
|
||||
'channelIndex': channel,
|
||||
'wantAck': want_ack,
|
||||
}
|
||||
if reply_id is not None:
|
||||
try:
|
||||
interface.sendData(raw_bytes, replyId=reply_id, **send_kwargs)
|
||||
except TypeError:
|
||||
logger.debug("System: replyId/replyID unsupported for sendData; sending without threaded reply")
|
||||
interface.sendData(raw_bytes, **send_kwargs)
|
||||
else:
|
||||
interface.sendData(raw_bytes, **send_kwargs)
|
||||
# Throttle the message sending to prevent spamming the device
|
||||
logger.debug(f"System: Sent raw bytes to {nodeid} on portnum {portnum} via Device{nodeInt}")
|
||||
time.sleep(responseDelay)
|
||||
@@ -2442,7 +2471,7 @@ def saveAllData():
|
||||
save_bbsBanList()
|
||||
logger.debug("Persistence: Ban list saved")
|
||||
|
||||
logger.info("Persistence: Save completed")
|
||||
#logger.info("Persistence: Save completed")
|
||||
except Exception as e:
|
||||
logger.error(f"Persistence: Save error: {e}")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user