import socket
|
|
import re
|
|
from flask import Flask, request, jsonify
|
|
from twilio.rest import Client
|
|
import time
|
|
import threading
|
|
import setproctitle
|
|
import json
|
|
|
|
# Set the custom process name
|
|
setproctitle.setproctitle("sms")
|
|
|
|
app = Flask(__name__)
|
|
|
|
# Set the expiration time in seconds (e.g., 90 seconds)
|
|
MESSAGE_EXPIRATION_TIME = 3600
|
|
|
|
# Set this variable to enable/disable private mode
|
|
private_mode = False # Change this value as needed
|
|
|
|
# List of callsigns allowed to send messages if private_mode is TRUE. Accepts ALL SSIDs for a CALLSIGN listed.
|
|
allowed_callsigns = ['CALLSIGN0', 'CALLSIGN1', 'CALLSIGN2'] # Add more callsigns as needed
|
|
|
|
tocall = 'APOSMS'
|
|
user_callsign = 'YOUR_CALLSIGN'
|
|
|
|
# Twilio credentials
|
|
TWILIO_ACCOUNT_SID = 'SID'
|
|
TWILIO_AUTH_TOKEN = 'AUTH'
|
|
TWILIO_PHONE_NUMBER = '+NUMBER' # Your Twilio phone number
|
|
TWILIO_PHONE_NUMBER_UK = '+UKNUMBER' #UK SUPPORT
|
|
|
|
# APRS credentials
|
|
APRS_CALLSIGN = 'CALL'
|
|
APRS_PASSCODE = 'PASS'
|
|
APRS_SERVER = 'rotate.aprs2.net'
|
|
APRS_PORT = 14580
|
|
|
|
# Initialize the socket
|
|
aprs_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
|
|
# Declare socket_ready as a global variable
|
|
socket_ready = False
|
|
|
|
# Dictionary to store the last number an APRS user messaged (callsign: last_number)
|
|
last_message_number = {}
|
|
|
|
# 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 = 90 # Adjust this as needed
|
|
|
|
MAX_RETRIES = 4 # Adjust this as needed
|
|
|
|
def handle_alias_update(from_callsign, verbose_message):
|
|
global alias_map, reverse_alias_map # Access the global dictionaries
|
|
|
|
# Extract alias information from the received message
|
|
alias_info = verbose_message.split('#alias', 1)[1].strip() # Remove the '#alias' prefix and trim whitespace
|
|
|
|
# Split the alias information into parts
|
|
alias_parts = alias_info.split()
|
|
|
|
if len(alias_parts) == 3:
|
|
action, alias_name, alias_phone = alias_parts
|
|
alias_name = alias_name.lower()
|
|
|
|
# Ensure the action is valid
|
|
if action in ("#add", "#remove"):
|
|
|
|
# Check if the alias phone is either 10 or 12 digits long #UK Support
|
|
if len(alias_phone) not in {10, 12} or not alias_phone.isdigit():
|
|
print("Invalid alias phone number. It must be either exactly 10 or 12 digits long.")
|
|
return
|
|
|
|
# Extract the SSID from the from_callsign
|
|
from_callsign_parts = from_callsign.split('-')
|
|
if len(from_callsign_parts) == 2:
|
|
from_callsign_strip = from_callsign_parts[0]
|
|
else:
|
|
from_callsign_strip = from_callsign # No SSID found
|
|
|
|
if from_callsign_strip in alias_map:
|
|
existing_aliases = alias_map[from_callsign_strip]
|
|
|
|
if action == "#add":
|
|
# Adding an alias
|
|
if alias_name in existing_aliases:
|
|
existing_phone = existing_aliases[alias_name]
|
|
if existing_phone != alias_phone:
|
|
# Alias name is found, but with a different phone number, update both name and phone
|
|
existing_aliases[alias_name] = alias_phone
|
|
|
|
elif alias_phone in existing_aliases.values():
|
|
# Alias phone number is found, update the associated alias name
|
|
for name, phone in existing_aliases.items():
|
|
if phone == alias_phone:
|
|
existing_aliases[alias_name] = alias_phone
|
|
del existing_aliases[name] # Remove the old alias name
|
|
break # Stop searching after the first match is found
|
|
|
|
else:
|
|
# Neither the alias name nor the alias phone is found, add the new alias
|
|
existing_aliases[alias_name] = alias_phone
|
|
|
|
elif action == "#remove":
|
|
# Removing an alias
|
|
if alias_name in existing_aliases:
|
|
if existing_aliases[alias_name] == alias_phone:
|
|
# Check if the provided alias and phone match the existing alias
|
|
alias_map[from_callsign_strip].pop(alias_name)
|
|
if not alias_map[from_callsign_strip]:
|
|
# If there are no more aliases for this callsign, remove the callsign entry
|
|
alias_map.pop(from_callsign_strip)
|
|
else:
|
|
print("Alias not found for removal:", alias_name, alias_phone)
|
|
else:
|
|
# If the callsign is not in the alias map, create a new entry
|
|
if action == "#add":
|
|
alias_map[from_callsign_strip] = {alias_name: alias_phone}
|
|
|
|
# Update the reverse_alias_map to reflect the changes
|
|
reverse_alias_map = generate_reverse_alias_map(alias_map)
|
|
|
|
# Save the updated alias map to a file
|
|
save_alias_map_to_file(alias_map, alias_map_filename)
|
|
|
|
# Print a message indicating the update
|
|
print("Alias map updated:")
|
|
print(alias_map)
|
|
else:
|
|
print("Invalid action:", action)
|
|
else:
|
|
print("Invalid alias information:", alias_info)
|
|
|
|
def save_alias_map_to_file(alias_map, filename):
|
|
# Save the alias map to the specified file in JSON format with proper formatting
|
|
with open(filename, 'w') as file:
|
|
formatted_json = json.dumps(alias_map, indent=4)
|
|
file.write(formatted_json)
|
|
|
|
def send_ack_message(sender, message_id):
|
|
ack_message = 'ack{}'.format(message_id)
|
|
sender_length = len(sender)
|
|
spaces_after_sender = ' ' * max(0, 9 - sender_length)
|
|
ack_packet_format = '{}>{}::{}{}:{}\r\n'.format(APRS_CALLSIGN, tocall, sender, spaces_after_sender, ack_message)
|
|
ack_packet = ack_packet_format.encode()
|
|
aprs_socket.sendall(ack_packet)
|
|
print("Sent ACK to {}: {}".format(sender, ack_message))
|
|
print("Outgoing ACK packet: {}".format(ack_packet.decode()))
|
|
|
|
def send_rej_message(sender, message_id):
|
|
rej_message = 'rej{}'.format(message_id)
|
|
sender_length = len(sender)
|
|
spaces_after_sender = ' ' * max(0, 9 - sender_length)
|
|
rej_packet_format = '{}>{}::{}{}:{}\r\n'.format(APRS_CALLSIGN, tocall, sender, spaces_after_sender, rej_message)
|
|
rej_packet = rej_packet_format.encode()
|
|
aprs_socket.sendall(rej_packet)
|
|
print("Sent REJ to {}: {}".format(sender, rej_message))
|
|
print("Outgoing REJ packet: {}".format(rej_packet.decode()))
|
|
|
|
def send_aprs_messages(callsign, from_phone_number, sender_phone_number, aprs_message, last_message_id):
|
|
#New Chunk Method
|
|
chunk_size = 67
|
|
|
|
# Additional characters for portion information
|
|
portion_info_chars = 4
|
|
|
|
# Calculate the constant value considering portion information
|
|
constant_value = chunk_size - len(sender_phone_number) - 2 - portion_info_chars
|
|
|
|
# Split the APRS message into chunks of 67 characters
|
|
message_chunks = [aprs_message[i:i + constant_value] for i in range(0, len(aprs_message), constant_value)]
|
|
#End Chunk Method
|
|
|
|
#New Portion Calc
|
|
total_chunks = len(message_chunks)
|
|
|
|
# Get the last APRS message ID sent to this user
|
|
last_message_id = user_last_message_id.get(from_phone_number, 0)
|
|
user_last_message_id[from_phone_number] = last_message_id
|
|
|
|
|
|
# Initialize a separate counter variable for the message ID within the loop
|
|
current_message_id = last_message_id
|
|
|
|
for i, chunk in enumerate(message_chunks):
|
|
# Calculate portion information
|
|
portion_info = " {}/{}".format(i + 1, total_chunks) if total_chunks > 1 else ""
|
|
|
|
print("Chunks", len(message_chunks))
|
|
# Format the APRS packet and send it to the APRS server
|
|
aprs_packet = format_aprs_packet(callsign, "@{}{} {}{}".format(sender_phone_number, portion_info, chunk, "{" + str(current_message_id)))
|
|
print("chunks ID: ", current_message_id)
|
|
print(aprs_packet)
|
|
aprs_socket.sendall(aprs_packet.encode())
|
|
|
|
# Not a good delay, maybe seek alternative options.
|
|
time.sleep(10) # Sleeping here allows time for incoming ack before retry
|
|
print("sleep ID: ", current_message_id)
|
|
retry_count = 0
|
|
ack_received = False
|
|
|
|
while retry_count < MAX_RETRIES and not ack_received:
|
|
if str(current_message_id) in received_acks.get(callsign, set()):
|
|
print("Message ACK received. No further retries needed.")
|
|
ack_received = True
|
|
retry_count = 0
|
|
received_acks.get(callsign, set()).discard(str(current_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)
|
|
|
|
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.")
|
|
|
|
# Increment the counter variable for the next chunk
|
|
current_message_id += 1
|
|
|
|
# Update the last_message_id outside the loop
|
|
last_message_id += len(message_chunks) - 1
|
|
|
|
user_last_message_id[from_phone_number] = last_message_id
|
|
|
|
print("Last ID: ", last_message_id)
|
|
print("Next ID: ", current_message_id)
|
|
print(user_last_message_id)
|
|
|
|
|
|
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)
|
|
|
|
try:
|
|
# Send SMS using the Twilio API
|
|
message = client.messages.create(
|
|
body="@{} {}".format(from_callsign, body_message),
|
|
from_=twilio_phone_number,
|
|
to=to_phone_number
|
|
)
|
|
print("SMS sent successfully.")
|
|
print("SMS SID:", message.sid)
|
|
except Exception as e:
|
|
print("Error sending SMS:", str(e))
|
|
|
|
|
|
def send_sms_uk(twilio_phone_number_uk, to_phone_number, from_callsign, body_message): #UK SUPPORT
|
|
# Initialize the Twilio client
|
|
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
|
|
|
|
try:
|
|
# Send SMS using the Twilio API
|
|
message = client.messages.create(
|
|
body="@{} {}".format(from_callsign, body_message),
|
|
from_=twilio_phone_number_uk,
|
|
to=to_phone_number
|
|
)
|
|
print("SMS sent successfully.")
|
|
print("SMS SID:", message.sid)
|
|
except Exception as e:
|
|
print("Error sending SMS:", str(e))
|
|
|
|
def format_aprs_packet(callsign, message):
|
|
sender_length = len(callsign)
|
|
spaces_after_sender = ' ' * max(0, 9 - sender_length) #1,9 - Changed 9-16
|
|
aprs_packet_format = '{}>{}::{}{}:{}\r\n'.format(APRS_CALLSIGN, tocall, callsign, spaces_after_sender, message)
|
|
return aprs_packet_format
|
|
|
|
def load_alias_map_from_file(filename):
|
|
try:
|
|
with open(filename, 'r') as file:
|
|
alias_map = json.load(file)
|
|
return alias_map
|
|
except (FileNotFoundError, json.JSONDecodeError):
|
|
return {} # Return an empty dictionary if the file is not found or cannot be parsed
|
|
|
|
# Define the filename for the alias map file
|
|
alias_map_filename = '/root/app/sms_map.json'
|
|
|
|
# Load the alias map from the file
|
|
alias_map = load_alias_map_from_file(alias_map_filename)
|
|
|
|
# Create a new dictionary to store the reverse mapping of phone numbers and aliases to callsigns
|
|
def generate_reverse_alias_map(alias_map):
|
|
reverse_alias_map = {}
|
|
for callsign, aliases_and_numbers in alias_map.items():
|
|
reverse_alias_map[callsign] = {}
|
|
for alias, phone_number in aliases_and_numbers.items():
|
|
reverse_alias_map[callsign][phone_number] = alias
|
|
return reverse_alias_map
|
|
|
|
# Whenever you update the alias map, you can regenerate the reverse_alias_map
|
|
# For example, after updating the alias map with new data, call this function
|
|
reverse_alias_map = generate_reverse_alias_map(alias_map)
|
|
|
|
|
|
def extract_sender_phone_number(from_phone_number):
|
|
# Extract the phone number from the sender's phone number based on the prefix
|
|
if from_phone_number.startswith('+1'):
|
|
return from_phone_number[2:]
|
|
elif from_phone_number.startswith('+44'):
|
|
return from_phone_number[1:]
|
|
else:
|
|
return from_phone_number[-10:]
|
|
|
|
|
|
def is_message_expired(timestamp):
|
|
return time.time() - timestamp > MESSAGE_EXPIRATION_TIME
|
|
|
|
|
|
@app.route('/sms', methods=['POST'])
|
|
def receive_sms():
|
|
global last_message_number #Questioning this. Consider options later.
|
|
# Parse the incoming SMS message
|
|
data = request.form
|
|
from_phone_number = data['From']
|
|
body_message = data['Body']
|
|
print (body_message)
|
|
|
|
if body_message.startswith('@'):
|
|
parts = body_message.split(' ', 1)
|
|
if len(parts) == 2:
|
|
# Extract the phone number from the sender's phone number
|
|
sender_phone_number = extract_sender_phone_number(from_phone_number)
|
|
callsign = parts[0][1:].upper()
|
|
aprs_message = parts[1]
|
|
print(callsign)
|
|
|
|
last_message_number[sender_phone_number] = callsign #Questioning this. Consider options later.
|
|
print(last_message_number)
|
|
|
|
|
|
# Get the last APRS message ID sent to this user
|
|
last_message_id = user_last_message_id.get(from_phone_number, 0)
|
|
last_message_id += 1
|
|
user_last_message_id[from_phone_number] = last_message_id
|
|
print("RX SMS ID: ", last_message_id)
|
|
print("RX USR ID: ", user_last_message_id)
|
|
|
|
|
|
# Extract the SSID from the from_callsign
|
|
from_callsign_parts = callsign.split('-')
|
|
if len(from_callsign_parts) == 2:
|
|
from_callsign_strip = from_callsign_parts[0]
|
|
else:
|
|
from_callsign_strip = callsign # No SSID found
|
|
print("No SSID Found")
|
|
|
|
# Use the reverse alias mapping to check if the sender's phone number has an associated alias
|
|
alias = reverse_alias_map.get(from_callsign_strip, {}).get(sender_phone_number.lower())
|
|
if alias:
|
|
sender_phone_number = alias
|
|
|
|
# Format and send APRS packets
|
|
send_aprs_messages(callsign, from_phone_number, sender_phone_number, aprs_message, last_message_id)
|
|
|
|
return jsonify({'status': 'success'})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'Invalid SMS format'})
|
|
else:
|
|
|
|
print ("no callsign found")
|
|
|
|
sender_phone_number = extract_sender_phone_number(from_phone_number)
|
|
callsign = last_message_number.get(sender_phone_number, None)
|
|
|
|
print("From:", from_phone_number[-10:])
|
|
print("From Full:", from_phone_number)
|
|
print("Dictionary:", last_message_number)
|
|
print("Callsign Found:", callsign)
|
|
|
|
if callsign:
|
|
# Extract the APRS message content
|
|
aprs_message = body_message
|
|
|
|
# Get the last APRS message ID sent to this user
|
|
last_message_id = user_last_message_id.get(from_phone_number, 0)
|
|
last_message_id += 1
|
|
user_last_message_id[from_phone_number] = last_message_id
|
|
|
|
# Extract the SSID from the from_callsign
|
|
from_callsign_parts = callsign.split('-')
|
|
if len(from_callsign_parts) == 2:
|
|
from_callsign_strip = from_callsign_parts[0]
|
|
else:
|
|
from_callsign_strip = callsign # No SSID found
|
|
|
|
# Use the reverse alias mapping to check if the sender's phone number has an associated alias
|
|
alias = reverse_alias_map.get(from_callsign_strip, {}).get(sender_phone_number.lower())
|
|
if alias:
|
|
sender_phone_number = alias
|
|
|
|
# Format and send APRS packets
|
|
send_aprs_messages(callsign, from_phone_number, sender_phone_number, aprs_message, last_message_id)
|
|
|
|
|
|
return jsonify({'status': 'success'})
|
|
else:
|
|
return jsonify({'status': 'error', 'message': 'No associated callsign found for the sender\'s phone number'})
|
|
|
|
def establish_aprs_connection():
|
|
global aprs_socket, socket_ready
|
|
|
|
while True:
|
|
try:
|
|
# Initialize the socket and connect to the APRS server
|
|
aprs_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
|
|
aprs_socket.connect((APRS_SERVER, APRS_PORT))
|
|
print("Connected to APRS server with callsign: {}".format(APRS_CALLSIGN))
|
|
|
|
# Send login information with APRS callsign and passcode
|
|
login_str = 'user {} pass {} vers SMS-Gateway 1.4 Beta\r\n'.format(APRS_CALLSIGN, APRS_PASSCODE)
|
|
aprs_socket.sendall(login_str.encode())
|
|
print("Sent login information.")
|
|
|
|
# Set the socket_ready flag to indicate that the socket is ready for keepalives
|
|
socket_ready = True
|
|
|
|
# If the connection was successful, break out of the loop
|
|
break
|
|
|
|
except socket.error as e:
|
|
print("Socket error:", str(e))
|
|
socket_ready = False
|
|
time.sleep(1) # Wait for a while before attempting to reconnect
|
|
|
|
except Exception as e:
|
|
print("Error connecting to APRS server: {}".format(e))
|
|
socket_ready = False
|
|
time.sleep(1) # Wait for a while before attempting to reconnect
|
|
|
|
def receive_aprs_messages():
|
|
global socket_ready, last_message_number, alias_map # Declare that you're using the global variable
|
|
|
|
while True:
|
|
try:
|
|
if not socket_ready:
|
|
# Attempt to establish a new connection to the APRS server
|
|
establish_aprs_connection()
|
|
|
|
buffer = ""
|
|
try:
|
|
while True:
|
|
data = aprs_socket.recv(1024)
|
|
if not data:
|
|
break
|
|
|
|
# Add received data to the buffer
|
|
buffer += data.decode()
|
|
|
|
# Split buffer into lines
|
|
lines = buffer.split('\n')
|
|
|
|
# Process each line
|
|
for line in lines[:-1]:
|
|
if line.startswith('#'):
|
|
continue
|
|
|
|
# Process APRS message
|
|
print("Received raw APRS packet: {}".format(line.strip()))
|
|
parts = line.strip().split(':')
|
|
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 verbose_message starts with ":SMS:" (or the appropriate APRS_CALLSIGN)
|
|
if message_text.startswith(":{}".format(APRS_CALLSIGN)):
|
|
|
|
# Check if the message contains "{"
|
|
if "{" in message_text[-6:]:
|
|
message_id = message_text.split('{')[1]
|
|
|
|
# Remove the first 11 characters from the message to exclude the "Callsign :" prefix
|
|
verbose_message = message_text[11:].split('{')[0].strip()
|
|
|
|
# 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))))
|
|
if not callsign_pattern.match(from_callsign):
|
|
print("Unauthorized sender:", from_callsign)
|
|
send_rej_message(from_callsign, message_id)
|
|
continue # Skip processing messages from unauthorized senders
|
|
|
|
# Display verbose message content
|
|
print("From: {}".format(from_callsign))
|
|
print("Message: {}".format(verbose_message))
|
|
print("Message ID: {}".format(message_id))
|
|
print(user_last_message_id)
|
|
|
|
# Initialize match
|
|
match = None
|
|
|
|
# Check if the verbose message contains the desired format with a number or an alias
|
|
#alias_pattern = r'#alias (.+)'
|
|
alias_pattern = re.compile(r'#alias (.+)', re.IGNORECASE)
|
|
|
|
alias_match = re.search(alias_pattern, verbose_message)
|
|
print("alias pattern")
|
|
if alias_match:
|
|
print("did we make it to 1")
|
|
# Call a function to handle alias updates
|
|
handle_alias_update(from_callsign, verbose_message.lower()) #added lower
|
|
else:
|
|
pattern = r'@(\d{10}|\w+) (.+)'
|
|
match = re.match(pattern, verbose_message)
|
|
|
|
|
|
# Send ACK
|
|
send_ack_message(from_callsign, message_id)
|
|
print("did we send this ack?")
|
|
|
|
if match:
|
|
recipient = match.group(1)
|
|
|
|
# Update the dictionary with the last message number for the callsign
|
|
|
|
# Extract the SSID from the from_callsign
|
|
from_callsign_parts = from_callsign.split('-')
|
|
if len(from_callsign_parts) == 2:
|
|
from_callsign_strip = from_callsign_parts[0]
|
|
else:
|
|
from_callsign_strip = from_callsign # No SSID found
|
|
|
|
# Use the reverse alias mapping to check if the sender's phone number has an associated alias
|
|
alias = alias_map.get(from_callsign_strip, {}).get(recipient.lower())
|
|
if alias:
|
|
recipient = alias
|
|
print("Recipient:", recipient)
|
|
|
|
|
|
#last_message_number[recipient.lower()] = from_callsign
|
|
print ("To #", recipient)
|
|
print ("From", from_callsign)
|
|
print ("Dictionary", last_message_number)
|
|
|
|
aprs_message = match.group(2)
|
|
|
|
# Check if the recipient is a 10-digit number or an alias
|
|
if recipient.isdigit():
|
|
# Recipient is a 10-digit number
|
|
phone_number = recipient
|
|
last_message_number[recipient.lower()] = from_callsign
|
|
|
|
else:
|
|
# Recipient is an alias
|
|
phone_number = alias_map.get(from_callsign, {}).get(recipient)
|
|
print("phone", phone_number)
|
|
|
|
if phone_number:
|
|
# Get the last APRS message ID sent to this user
|
|
last_message_id = user_last_message_id.get(from_callsign, 0)
|
|
last_message_id += 1
|
|
user_last_message_id[from_callsign] = last_message_id
|
|
|
|
if not phone_number:
|
|
print("Recipient not found in alias map or not a 10 or 12 digit number: {}".format(recipient))
|
|
continue # Skip processing the current message and move on to the next one in the loop
|
|
|
|
# Check for duplicate messages
|
|
messages_for_callsign = received_aprs_messages.get(from_callsign, [])
|
|
current_time = time.time()
|
|
|
|
|
|
# Check if the message has been received in the last 90 seconds
|
|
if any(not is_message_expired(stored_timestamp) and stored_message_id == message_id for stored_message, stored_message_id, stored_timestamp in messages_for_callsign):
|
|
print("Message received in the last 3600 seconds. Skipping further processing.")
|
|
|
|
#UK SUPPORT
|
|
else:
|
|
# Mark the message as received
|
|
received_aprs_messages.setdefault(from_callsign, []).append((aprs_message, message_id, current_time))
|
|
print(received_aprs_messages)
|
|
|
|
if len(phone_number) == 12 and phone_number.startswith("44"):
|
|
# UK phone number format: 12 digits and starts with "44"
|
|
send_sms_uk(TWILIO_PHONE_NUMBER_UK, phone_number, from_callsign, aprs_message)
|
|
else:
|
|
# Default behavior
|
|
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)
|
|
print("Process MSG ID")
|
|
|
|
# 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)
|
|
print("Process ACK ID")
|
|
|
|
pass
|
|
# Send ACK
|
|
# The last line might be an incomplete packet, so keep it in the buffer
|
|
buffer = lines[-1]
|
|
|
|
except Exception as e:
|
|
print("Error receiving APRS messages: {}".format(e))
|
|
socket_ready = False # Reset the socket_ready flag to indicate a disconnected state
|
|
|
|
# Close the socket to release system resources
|
|
if aprs_socket:
|
|
aprs_socket.close()
|
|
|
|
time.sleep(1) # Wait for a while before attempting to reconnect
|
|
|
|
except Exception as e:
|
|
print("Error in receive_aprs_messages:", str(e))
|
|
|
|
|
|
#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)
|
|
|
|
# Update your records or take any other necessary action
|
|
|
|
def send_keepalive():
|
|
global socket_ready # Declare that you're using the global variable
|
|
|
|
while True:
|
|
try:
|
|
if socket_ready:
|
|
# Send a keepalive packet to the APRS server
|
|
keepalive_packet = '#\r\n'
|
|
aprs_socket.sendall(keepalive_packet.encode())
|
|
print("Sent keepalive packet.")
|
|
except socket.error as e:
|
|
print("Socket error while sending keepalive:", str(e))
|
|
# Reestablish the connection to the APRS server
|
|
establish_aprs_connection()
|
|
except Exception as e:
|
|
print("Error while sending keepalive:", str(e))
|
|
# Reestablish the connection to the APRS server
|
|
establish_aprs_connection()
|
|
|
|
time.sleep(30) # Send keepalive every 10 seconds
|
|
|
|
def send_beacon():
|
|
global socket_ready # Declare that you're using the global variable
|
|
|
|
while True:
|
|
try:
|
|
if socket_ready:
|
|
# Send a keepalive packet to the APRS server
|
|
beacon_packet = '{}>{}:Your Position {}\r\n'.format(APRS_CALLSIGN, tocall, user_callsign)
|
|
status_beacon = '{}>{}:>Status Beacon\r\n'.format(APRS_CALLSIGN, tocall)
|
|
aprs_socket.sendall(beacon_packet.encode())
|
|
aprs_socket.sendall(status_beacon.encode())
|
|
|
|
print("Sent Beacon Packet.")
|
|
except Exception as e:
|
|
print("Error sending beacon:", str(e))
|
|
time.sleep(600) # Send beacon every 10 minutes
|
|
|
|
if __name__ == '__main__':
|
|
print("APRS bot is running. Waiting for APRS messages...")
|
|
|
|
# Establish the initial connection to the APRS server
|
|
establish_aprs_connection()
|
|
|
|
# Start a separate thread for sending keepalive packets
|
|
keepalive_thread = threading.Thread(target=send_keepalive)
|
|
keepalive_thread.start()
|
|
|
|
# Start a separate thread for sending keepalive packets
|
|
beacon_thread = threading.Thread(target=send_beacon)
|
|
beacon_thread.start()
|
|
|
|
# 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': 12345})
|
|
webhook_thread.start()
|
|
|
|
# Start listening for APRS messages
|
|
receive_aprs_messages()
|