import socket
import RPi.GPIO as GPIO
import time
import smbus

# ADS7830 address
ADC_address = 0x48

# Define some device parameters
I2C_ADDR = 0x27  # I2C address
LCD_WIDTH = 16

# Define some device constants
LCD_CHR = 1  # Mode - Sending data
LCD_CMD = 0  # Mode - Sending command

LCD_LINE_1 = 0x80 | 0x0   # Instruction to go to beginning first line
LCD_LINE_2 = 0x80 | 0x40  # Instruction to go to beginning second line

LCD_BACKLIGHT = 0x08  # On
# LCD_BACKLIGHT = 0x00  # Off

ENABLE_HIGH = 0b0000_0100  # Enable bit value

E_PULSE = 0.0002
E_DELAY = 0.0002

# Initialize I2C bus
i2c = smbus.SMBus(1)

# GPIO setup
BUZZER_PIN = 4
LED_PIN = 17
GPIO.setmode(GPIO.BCM)
GPIO.setup(LED_PIN, GPIO.OUT)
GPIO.output(LED_PIN, GPIO.LOW)
GPIO.setup(BUZZER_PIN, GPIO.OUT)

# function to send command to lcd
def send_instruction(byte):
    set_data_bits(byte, LCD_CMD)

# function to send character to lcd
def send_character(byte):
    set_data_bits(byte, LCD_CHR)

# start up / initialize lcd
def lcd_init():
    clear()
    # Initialise display
    send_byte_with_e_toggle(0b0011_0000)
    send_byte_with_e_toggle(0b0011_0000)

    send_byte_with_e_toggle(0b0010_0000)

    send_instruction(0x06)  # 000110 Cursor move direction
    send_instruction(0x0C)  # 001100 Dislay on, Cursor Off, Blink Off
    send_instruction(0x28)  # 101000 Data length, number of lines, font size
    clear()
    time.sleep(E_DELAY)

def clear(line=None):
    if line is None:
        send_instruction(0x01)  # 000001 Clear display
    elif line == 1:
        send_string(" " * LCD_WIDTH, LCD_LINE_1)
    elif line == 2:
        send_string(" " * LCD_WIDTH, LCD_LINE_2)
    time.sleep(E_DELAY)

# sets data bits for communication with lcd
def set_data_bits(bits, mode):
    bits_high = mode | (bits & 0xF0) | LCD_BACKLIGHT
    bits_low = mode | ((bits << 4) & 0xF0) | LCD_BACKLIGHT
    send_byte_with_e_toggle(bits_high)
    send_byte_with_e_toggle(bits_low)

# sends byte to lcd
def send_byte_with_e_toggle(bits):
    # Toggle enable
    time.sleep(E_DELAY)
    i2c.write_byte(I2C_ADDR, bits | ENABLE_HIGH | LCD_BACKLIGHT)
    time.sleep(E_PULSE)
    i2c.write_byte(I2C_ADDR, bits & ~ENABLE_HIGH | LCD_BACKLIGHT)
    time.sleep(E_DELAY)

# Send a string to lcd at specified line
def send_string(message, line=LCD_LINE_1):
    # Set cursor to the specified line
    send_instruction(line)

    # Send each character of the message to the LCD display
    for char in message:
        send_character(ord(char))

def setup_socket_server():
    with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
        s.bind(('0.0.0.0', 65432))
        s.listen()
        print("Server listening on 0.0.0.0:65432")
        while True:
            conn, addr = s.accept()
            with conn:
                print(f"Connected by {addr}")
                receive_data(conn)

def receive_data(conn):
    cat_count = 0  # Initialize cat count
    prev_cat_count = -1  # Initialize previous cat count
    while True:
        data = conn.recv(1024)
        if not data:
            break
        decoded_data = data.decode()
        print(f"Received: {decoded_data}")
        update_display(decoded_data, cat_count, prev_cat_count)

def sound_buzzer():
    frequency = 20000  # 20 kHz
    period = 1.0 / frequency
    duty_cycle = 0.5  # 50% duty cycle

    # Calculate the high and low times
    high_time = period * duty_cycle
    low_time = period * (1.0 - duty_cycle)

    start_time = time.time()
    # Run the PWM signal for 5 seconds
    while time.time() - start_time < 0.1:
        GPIO.output(BUZZER_PIN, GPIO.HIGH)
        time.sleep(high_time)
        GPIO.output(BUZZER_PIN, GPIO.LOW)
        time.sleep(low_time)

def update_display(decoded_data, cat_count, prev_cat_count):
    # Check if 'Cat' is in the received data
    if 'Cat' in decoded_data:
        cat_count += 1  # Increment cat count
        # Update LCD display with cat count only if it has changed
        if cat_count != prev_cat_count:
            send_string(f"Cat Count: {cat_count}", LCD_LINE_1)
            prev_cat_count = cat_count
    else:
        # No cat detected, clear LCD display
        cat_count = 0  # Reset cat count
        prev_cat_count = -1  # Reset previous cat count
        send_string(f"Cat Count: {cat_count}", LCD_LINE_1)
    # Check if both 'Cat' and 'Pond' are in the received data
    if 'Cat' in decoded_data and 'Pond' in decoded_data:
        GPIO.output(LED_PIN, GPIO.HIGH)
        print("Cat and pond detected: LED ON")
        send_string(f"Cat & Pond:  YES", LCD_LINE_2)
        sound_buzzer()
    else:
        GPIO.output(LED_PIN, GPIO.LOW)
        print("Cat and pond not detected: LED OFF")
        send_string(f"Cat & Pond:   NO", LCD_LINE_2)
        # Stop the buzzer sound
        GPIO.output(BUZZER_PIN, GPIO.LOW)



if __name__ == "__main__":
    try:
        setup_socket_server()
    except KeyboardInterrupt:
        pass
    finally:
        clear()
        GPIO.cleanup()
        print("GPIO cleanup and server stopped.")
