Browse Source

Update sms.py

Updates various fixes
main
Mike 2 years ago
committed by GitHub
parent
commit
527b66cd1f
No known key found for this signature in database GPG Key ID: 4AEE18F83AFDEB23
1 changed files with 343 additions and 112 deletions
  1. +343
    -112
      sms.py

+ 343
- 112
sms.py View File

@ -2,6 +2,12 @@ import socket
import re
from flask import Flask, request, jsonify
from twilio.rest import Client
import time
import threading
import setproctitle
# Set the custom process name
setproctitle.setproctitle("sms")
app = Flask(__name__)
@ -13,27 +19,43 @@ allowed_callsigns = ['CALLSIGN0', 'CALLSIGN1', 'CALLSIGN2'] # Add more callsign
# Twilio credentials
TWILIO_ACCOUNT_SID = 'SID'
TWILIO_AUTH_TOKEN = 'TOKEN'
TWILIO_AUTH_TOKEN = 'AUTH'
TWILIO_PHONE_NUMBER = '+NUMBER' # Your Twilio phone number
# APRS credentials
APRS_CALLSIGN = 'CALLSIGN'
APRS_PASSCODE = 'PASSCODE'
APRS_SERVER = 'roate.aprs2.net'
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 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 = '{}>APRS::{}{}:{}\r\n'.format(APRS_CALLSIGN, sender, spaces_after_sender, ack_message)
ack_packet_format = '{}>APOSMS::{}{}:{}\r\n'.format(APRS_CALLSIGN, 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))
@ -43,12 +65,13 @@ 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 = '{}>APRS::{}{}:{}\r\n'.format(APRS_CALLSIGN, sender, spaces_after_sender, rej_message)
rej_packet_format = '{}>APOSMS::{}{}:{}\r\n'.format(APRS_CALLSIGN, 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_sms(twilio_phone_number, to_phone_number, from_callsign, body_message):
# Initialize the Twilio client
client = Client(TWILIO_ACCOUNT_SID, TWILIO_AUTH_TOKEN)
@ -68,15 +91,15 @@ 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(0, 9 - sender_length) #1,9 - Changed 9-16
aprs_packet_format = '{}>APOSMS::{}{}:{}\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.
# Add more entries as needed.
'alias1': '9876543210', # 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.
}
def find_phone_number_from_alias(alias):
@ -92,155 +115,363 @@ def receive_sms():
data = request.form
from_phone_number = data['From']
body_message = data['Body']
# If the message is in the correct format, the function extracts the callsign and APRS message content from the SMS body.
print (body_message)
if body_message.startswith('@'):
parts = body_message.split(' ', 1)
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()
aprs_message = parts[1]
# Get the last APRS message ID sent to this user
last_message_id = user_last_message_id.get(from_phone_number, 0)
# Increment the message ID to avoid duplicate messages
last_message_id += 1
user_last_message_id[from_phone_number] = last_message_id
# Use the reverse alias mapping to check if the sender's phone number has an associated alias
alias = reverse_alias_map.get(sender_phone_number.lower())
if alias:
sender_phone_number = alias
# If an alias is found, use it; otherwise, use the phone number itself as the alias
if alias:
sender_phone_number = alias
# 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("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_count = 0
ack_received = False
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
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)
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:
return jsonify({'status': 'error', 'message': 'Invalid SMS format'})
else:
return jsonify({'status': 'error', 'message': 'SMS does not start with "@" symbol'})
print ("no callsign found")
# Message without @callsign prefix
callsign = last_message_number.get(from_phone_number[-10:], None)
sender_phone_number = from_phone_number[-10:]
print("From:", from_phone_number[-10:])
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
# Use the reverse alias mapping to check if the sender's phone number has an associated alias
alias = reverse_alias_map.get(sender_phone_number.lower())
if alias:
sender_phone_number = alias
def receive_aprs_messages():
# Connect to the APRS server
aprs_socket.connect((APRS_SERVER, APRS_PORT))
print("Connected to APRS server with callsign: {}".format(APRS_CALLSIGN))
# 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())
# Send login information with APRS callsign and passcode
login_str = 'user {} pass {} vers SMS-Gateway 0.1b\r\n'.format(APRS_CALLSIGN, APRS_PASSCODE)
aprs_socket.sendall(login_str.encode())
print("Sent login information.")
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
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()
# 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
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))
# 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)
if match:
recipient = match.group(1)
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
else:
# Recipient is an alias
phone_number = find_phone_number_from_alias(recipient)
retry_count = 0
ack_received = False
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
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)
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 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.")
# Send SMS
send_sms(TWILIO_PHONE_NUMBER, phone_number, from_callsign, aprs_message)
return jsonify({'status': 'success'})
else:
return jsonify({'status': 'error', 'message': 'No associated callsign found for the sender\'s phone number'})
else:
print("Recipient not found in alias map or not a 10-digit number: {}".format(recipient))
def establish_aprs_connection():
global aprs_socket, socket_ready
pass
# Send ACK
# The last line might be an incomplete packet, so keep it in the buffer
buffer = lines[-1]
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))
except Exception as e:
print("Error receiving APRS messages: {}".format(e))
# Send login information with APRS callsign and passcode
login_str = 'user {} pass {} vers SMS-Gateway 1.0 Beta\r\n'.format(APRS_CALLSIGN, APRS_PASSCODE)
aprs_socket.sendall(login_str.encode())
print("Sent login information.")
finally:
# Close the socket connection when done
aprs_socket.close()
# 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 # 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 the message contains "{"
if "{" in message_text:
message_id = message_text.split('{')[1]
else:
message_id = '1'
if ":" in message_text and APRS_CALLSIGN in message_text:
pass
# 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)
# 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)
if match:
recipient = match.group(1)
# Update the dictionary with the last message number for the callsign
# Use the reverse alias mapping to check if the sender's phone number has an associated alias
alias = alias_map.get(recipient.lower())
if alias:
recipient = alias
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
else:
# Recipient is an alias
phone_number = find_phone_number_from_alias(recipient)
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
else:
print("Recipient not found in alias map or not a 10-digit number: {}".format(recipient))
# 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)
# 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)
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 = 'POSITION BEACON\r\n'
status_beacon = 'STATUS BEACON\r\n'
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...")
# 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': 5000})
webhook_thread.start()
# Establish the initial connection to the APRS server
establish_aprs_connection()
# Start listening for APRS messages
receive_aprs_messages()

Loading…
Cancel
Save