|
|
|
@ -2,6 +2,7 @@ import socket |
|
|
|
import re |
|
|
|
from flask import Flask, request, jsonify |
|
|
|
from twilio.rest import Client |
|
|
|
import time |
|
|
|
|
|
|
|
app = Flask(__name__) |
|
|
|
|
|
|
|
@ -19,7 +20,7 @@ TWILIO_PHONE_NUMBER = '+NUMBER' # Your Twilio phone number |
|
|
|
# APRS credentials |
|
|
|
APRS_CALLSIGN = 'CALLSIGN' |
|
|
|
APRS_PASSCODE = 'PASSCODE' |
|
|
|
APRS_SERVER = 'roate.aprs2.net' |
|
|
|
APRS_SERVER = 'rotate.aprs2.net' |
|
|
|
APRS_PORT = 14580 |
|
|
|
|
|
|
|
# Initialize the socket |
|
|
|
@ -28,6 +29,17 @@ aprs_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) |
|
|
|
# Dictionary to store the last received APRS message ID for each user |
|
|
|
user_last_message_id = {} |
|
|
|
|
|
|
|
processed_message_ids = set() |
|
|
|
|
|
|
|
# Outside the main loop, initialize a dictionary to store message history |
|
|
|
received_aprs_messages = {} |
|
|
|
|
|
|
|
received_acks = {} |
|
|
|
|
|
|
|
RETRY_INTERVAL = 60 # Adjust this as needed |
|
|
|
|
|
|
|
MAX_RETRIES = 3 # Adjust this as needed |
|
|
|
|
|
|
|
|
|
|
|
def send_ack_message(sender, message_id): |
|
|
|
ack_message = 'ack{}'.format(message_id) |
|
|
|
@ -49,6 +61,7 @@ def send_rej_message(sender, message_id): |
|
|
|
print("Sent REJ to {}: {}".format(sender, rej_message)) |
|
|
|
print("Outgoing REJ packet: {}".format(rej_packet.decode())) |
|
|
|
|
|
|
|
|
|
|
|
def send_sms(twilio_phone_number, to_phone_number, from_callsign, body_message): |
|
|
|
# Initialize the Twilio client |
|
|
|
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN) |
|
|
|
@ -68,14 +81,14 @@ def send_sms(twilio_phone_number, to_phone_number, from_callsign, body_message): |
|
|
|
|
|
|
|
def format_aprs_packet(callsign, message): |
|
|
|
sender_length = len(callsign) |
|
|
|
spaces_after_sender = ' ' * max(0, 9 - sender_length) |
|
|
|
aprs_packet_format = '{}>NA7Q::{}{}:{}\r\n'.format(APRS_CALLSIGN, callsign, spaces_after_sender, message) |
|
|
|
spaces_after_sender = ' ' * max(1, 9 - sender_length) |
|
|
|
aprs_packet_format = '{}>APRS::{}{}:{}\r\n'.format(APRS_CALLSIGN, callsign, spaces_after_sender, message) |
|
|
|
return aprs_packet_format |
|
|
|
|
|
|
|
# Dictionary to store the mapping of aliases (callsigns) to phone numbers |
|
|
|
alias_map = { |
|
|
|
'alias1': '1234567890', # Replace 'alias1' with the desired alias and '1234567890' with the corresponding phone number. |
|
|
|
'alias2': '0987654321', # Add more entries as needed for other aliases and phone numbers. |
|
|
|
'laura': '5032985265', # Replace 'alias1' with the desired alias and '1234567890' with the corresponding phone number. |
|
|
|
'alias2': '9876543210', # Add more entries as needed for other aliases and phone numbers. |
|
|
|
# Add more entries as needed. |
|
|
|
} |
|
|
|
|
|
|
|
@ -99,7 +112,7 @@ def receive_sms(): |
|
|
|
if len(parts) == 2: |
|
|
|
# Extract the 10-digit phone number from the sender's phone number |
|
|
|
sender_phone_number = from_phone_number[-10:] |
|
|
|
callsign = parts[0][1:].upper() # Convert to uppercase |
|
|
|
callsign = parts[0][1:].upper() #Convert to UPPERCASE |
|
|
|
aprs_message = parts[1] |
|
|
|
|
|
|
|
# Get the last APRS message ID sent to this user |
|
|
|
@ -120,8 +133,34 @@ def receive_sms(): |
|
|
|
# Format the APRS packet and send it to the APRS server |
|
|
|
aprs_packet = format_aprs_packet(callsign, "@{} {}".format(sender_phone_number, aprs_message + "{" + str(last_message_id))) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
#print(user_last_message_id) |
|
|
|
#print(last_message_id) |
|
|
|
print("Sent APRS message to {}: {}".format(callsign, aprs_message)) |
|
|
|
print("Outgoing APRS packet: {}".format(aprs_packet.strip())) |
|
|
|
|
|
|
|
time.sleep(5) # Sleeping here allows time for incoming ack before retry |
|
|
|
|
|
|
|
# Retry sending the message if ACK is not received |
|
|
|
retry_count = 0 |
|
|
|
ack_received = False # Flag to track whether ACK is received |
|
|
|
|
|
|
|
while retry_count < MAX_RETRIES and not ack_received: |
|
|
|
if str(last_message_id) in received_acks.get(callsign, set()): |
|
|
|
print("Message ACK received. No further retries needed.") |
|
|
|
ack_received = True |
|
|
|
# Reset retry count and remove the ACK ID |
|
|
|
retry_count = 0 |
|
|
|
received_acks.get(callsign, set()).discard(str(last_message_id)) |
|
|
|
else: |
|
|
|
print("ACK not received. Retrying in {} seconds.".format(RETRY_INTERVAL)) |
|
|
|
aprs_socket.sendall(aprs_packet.encode()) |
|
|
|
retry_count += 1 |
|
|
|
time.sleep(RETRY_INTERVAL) # Pause for the defined interval |
|
|
|
|
|
|
|
if ack_received: |
|
|
|
print("ACK received during retries. No further retries needed.") |
|
|
|
elif retry_count >= MAX_RETRIES: |
|
|
|
print("Max retries reached. No ACK received for the message.") |
|
|
|
|
|
|
|
return jsonify({'status': 'success'}) |
|
|
|
else: |
|
|
|
@ -165,15 +204,25 @@ def receive_aprs_messages(): |
|
|
|
if len(parts) >= 2: |
|
|
|
from_callsign = parts[0].split('>')[0].strip() |
|
|
|
message_text = ':'.join(parts[1:]).strip() |
|
|
|
|
|
|
|
# Extract and process ACK ID if present |
|
|
|
if "ack" in message_text: |
|
|
|
parts = message_text.split("ack", 1) |
|
|
|
if len(parts) == 2 and parts[1].isdigit(): |
|
|
|
ack_id = parts[1] |
|
|
|
process_ack_id(from_callsign, ack_id) |
|
|
|
# End RXd ACK ID for MSG Retries |
|
|
|
|
|
|
|
|
|
|
|
# Check if the message contains "{" |
|
|
|
if "{" in message_text: |
|
|
|
message_id = message_text.split('{')[1].strip('}') |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# Remove the first 11 characters from the message to exclude the "Callsign :" prefix |
|
|
|
verbose_message = message_text[11:].split('{')[0].strip() |
|
|
|
|
|
|
|
# If private_mode is enabled, check against allowed_callsigns; otherwise, process normally |
|
|
|
# Inside the receive_aprs_messages function |
|
|
|
if private_mode: |
|
|
|
# Use regular expression to match main callsign and accept all SSIDs |
|
|
|
callsign_pattern = re.compile(r'^({})(-\d+)?$'.format('|'.join(map(re.escape, allowed_callsigns)))) |
|
|
|
@ -186,13 +235,16 @@ def receive_aprs_messages(): |
|
|
|
print("From: {}".format(from_callsign)) |
|
|
|
print("Message: {}".format(verbose_message)) |
|
|
|
print("Message ID: {}".format(message_id)) |
|
|
|
print(user_last_message_id) |
|
|
|
|
|
|
|
|
|
|
|
# Check if the verbose message contains the desired format with a number or an alias |
|
|
|
pattern = r'@(\d{10}|\w+) (.+)' |
|
|
|
match = re.match(pattern, verbose_message) |
|
|
|
|
|
|
|
# Send ACK |
|
|
|
send_ack_message(from_callsign, message_id) |
|
|
|
send_ack_message(from_callsign, message_id) |
|
|
|
|
|
|
|
|
|
|
|
if match: |
|
|
|
recipient = match.group(1) |
|
|
|
@ -212,12 +264,32 @@ def receive_aprs_messages(): |
|
|
|
last_message_id = user_last_message_id.get(from_callsign, 0) |
|
|
|
last_message_id += 1 |
|
|
|
user_last_message_id[from_callsign] = last_message_id |
|
|
|
|
|
|
|
|
|
|
|
# Check for duplicate messages |
|
|
|
if (aprs_message, message_id) in received_aprs_messages.get(from_callsign, set()): |
|
|
|
print("Duplicate message detected. Skipping SMS sending.") |
|
|
|
send_ack_message(from_callsign, message_id) |
|
|
|
|
|
|
|
else: |
|
|
|
# Mark the message as received |
|
|
|
received_aprs_messages.setdefault(from_callsign, set()).add((aprs_message, message_id)) |
|
|
|
|
|
|
|
# Send SMS |
|
|
|
send_sms(TWILIO_PHONE_NUMBER, phone_number, from_callsign, aprs_message) |
|
|
|
|
|
|
|
# Add this line to mark the message ID as processed |
|
|
|
processed_message_ids.add(message_id) |
|
|
|
|
|
|
|
# else: |
|
|
|
print("Recipient not found in alias map or not a 10-digit number: {}".format(recipient)) |
|
|
|
|
|
|
|
|
|
|
|
# Extract and process ACK ID if present |
|
|
|
if message_text.startswith("ack"): |
|
|
|
ack_id = message_text[3:] # Remove the "ack" prefix |
|
|
|
process_ack_id(from_callsign, ack_id) |
|
|
|
|
|
|
|
else: |
|
|
|
print("Recipient not found in alias map or not a 10-digit number: {}".format(recipient)) |
|
|
|
|
|
|
|
pass |
|
|
|
# Send ACK |
|
|
|
@ -231,15 +303,17 @@ def receive_aprs_messages(): |
|
|
|
# Close the socket connection when done |
|
|
|
aprs_socket.close() |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
#Implementation for ack check with Message Retries #TODO |
|
|
|
def process_ack_id(from_callsign, ack_id): |
|
|
|
print("Received ACK from {}: {}".format(from_callsign, ack_id)) |
|
|
|
received_acks.setdefault(from_callsign, set()).add(ack_id) |
|
|
|
|
|
|
|
if __name__ == '__main__': |
|
|
|
print("APRS bot is running. Waiting for APRS messages...") |
|
|
|
|
|
|
|
# Run the Flask web application in a separate thread to handle incoming SMS messages |
|
|
|
from threading import Thread |
|
|
|
webhook_thread = Thread(target=app.run, kwargs={'host': '0.0.0.0', 'port': 5000}) |
|
|
|
webhook_thread = Thread(target=app.run, kwargs={'host': '23.138.32.57', 'port': 5000}) |
|
|
|
webhook_thread.start() |
|
|
|
|
|
|
|
# Start listening for APRS messages |
|
|
|
|