# Author: Pierce Parker # Date: 4/22/2024 # Program Name: lightclient.py # Description: A UDP client that sends motion detection events to a server and receives blink parameters. import socket import sys import struct import time from gpiozero import MotionSensor from datetime import datetime PIR = MotionSensor(4) # Utility Functions def create_packet(sequence_number, ack_number, ack, syn, fin, payload): """ Creates a custom UDP packet with a specified header and payload. Args: sequence_number (int): Sequence number. ack_number (int): Acknowledgment number. ack (int): ACK flag (1 or 0). syn (int): SYN flag (1 or 0). fin (int): FIN flag (1 or 0). payload (str): Data to include in the packet. Returns: bytes: Packed data ready for transmission. """ data = struct.pack('!I', sequence_number) data += struct.pack('!I', ack_number) flags = (ack << 2) | (syn << 1) | fin data += struct.pack('!I', flags) data += payload.encode() return data def parse_packet(packet): """ Parses a received UDP packet and extracts its components. Args: packet (bytes): The received packet data. Returns: tuple: Parsed sequence number, acknowledgment number, flags, and payload. """ sequence_number, ack_number, flags = struct.unpack('!III', packet[:12]) ack = (flags >> 2) & 1 syn = (flags >> 1) & 1 fin = flags & 1 payload = packet[12:].decode() return sequence_number, ack_number, ack, syn, fin, payload def get_time(): """Returns the current timestamp in the format 'YYYY-MM-DD-HH-MM-SS'.""" return datetime.now().strftime("%Y-%m-%d-%H-%M-%S") # Main Client Logic def main(): try: # Validate command-line arguments if len(sys.argv) != 7: print("Usage: python3 lightclient.py -s -p -l ") sys.exit(1) # Parse command-line arguments server_ip = sys.argv[2] port = int(sys.argv[4]) log_file = sys.argv[6] DURATION = .5 NUMBLINKS = 3 # Create a UDP socket sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM) server_address = (server_ip, port) sequence_number = 1 # Start Handshake print("[CLIENT] Sending connection request (SYN) to server...") sock.sendto(create_packet(sequence_number, 0, ack=0, syn=1, fin=0, payload="SYN"), server_address) with open(log_file, 'a') as f: f.write(f'{get_time()}: "SENT" {sequence_number} 0 [ACK:0 SYN:1 FIN:0] SYN message sent\n') sequence_number += 1 # Receive SYN-ACK print("[CLIENT] Waiting for connection acknowledgment (SYN-ACK) from server...") data, _ = sock.recvfrom(1024) server_seq, ack_number, ack, syn, fin, payload = parse_packet(data) with open(log_file, 'a') as f: f.write(f'{get_time()}: "RECEIVED" {server_seq} {ack_number} [ACK:{ack} SYN:{syn} FIN:{fin}] {payload}\n') # Send ACK print("[CLIENT] Confirming connection (ACK) to server...") sock.sendto(create_packet(sequence_number, server_seq + 1, ack=1, syn=0, fin=0, payload="ACK"), server_address) with open(log_file, 'a') as f: f.write(f'{get_time()}: "SENT" {sequence_number} {server_seq + 1} [ACK:1 SYN:0 FIN:0] ACK message sent\n') sequence_number += 1 # Send Blink Parameters print("[CLIENT] Sending blink parameters (count and duration) to server...") sock.sendto(create_packet(sequence_number, server_seq + 1, ack=1, syn=0, fin=0, payload=f"{NUMBLINKS} {DURATION}"), server_address) with open(log_file, 'a') as f: f.write(f'{get_time()}: "SENT" {sequence_number} {server_seq + 1} [ACK:1 SYN:0 FIN:0] Blink parameters sent\n') sequence_number += 1 # Receive Blink Parameters Acknowledgment print("[CLIENT] Waiting for acknowledgment of blink parameters from server...") data, _ = sock.recvfrom(1024) server_seq, ack_number, ack, syn, fin, payload = parse_packet(data) with open(log_file, 'a') as f: f.write(f'{get_time()}: "RECEIVED" {server_seq} {ack_number} [ACK:{ack} SYN:{syn} FIN:{fin}] {payload}\n') # Motion Detection Loop while True: try: print("[CLIENT] Monitoring for motion...") PIR.wait_for_motion() print("[CLIENT] Motion detected. Notifying server...") with open(log_file, 'a') as f: f.write(f"{get_time()}: Motion detected\n") sock.sendto( create_packet(sequence_number, server_seq + 1, ack=1, syn=0, fin=0, payload=":MotionDetected"), server_address ) with open(log_file, 'a') as f: f.write(f'{get_time()}: "SENT" {sequence_number} {server_seq + 1} [ACK:1 SYN:0 FIN:0] Motion detected message sent\n') sequence_number += 1 time.sleep(NUMBLINKS * DURATION) PIR.wait_for_no_motion() except KeyboardInterrupt: print("[CLIENT] Terminating connection (FIN)...") sock.sendto( create_packet(sequence_number, server_seq + 1, ack=1, syn=0, fin=1, payload="FIN"), server_address ) with open(log_file, 'a') as f: f.write(f'{get_time()}: "SENT" {sequence_number} {server_seq + 1} [ACK:1 SYN:0 FIN:1] Termination request sent\n') break finally: sock.close() print("[CLIENT] Connection closed.") if __name__ == "__main__": main()