# Description: Contains the main logic of the SWBMS Project(Smart Waste Bin Management System Project)
# Date:16/10/2023

#--------------------import all the necessary library--------------------------

from machine import UART, Pin, I2C      #this is the library to intract with i/o pin Used for interacting with hardware pins, ADC (Analog-to-Digital Converter), and UART for serial communication.
from utime import ticks_ms, sleep,sleep_us,sleep_ms,ticks_us,time 
from mfrc522 import MFRC522
from hx711 import HX711
from gsmm import GSM
import _thread
from queue import Queue
import ure
import json
import _thread
import machine
import ubinascii
#------------------------------------------------------------------------------
#--------------variable initialisation------------------------------------------
global Start_Calibration_Flag
Debug = True
Start_Calibration_Flag = 0
# URLs for data communication
get_url = "http://3.28.131.115:8081/agb/" 
post_url = "http://3.28.131.115:8081/agb/saveBinData"
#-------------------------------------------------------------------------------

#Function for the load cell calibration
def LoadSensorCalibration():
    try:
        #white light: means calibration start
        Red_LED.high()
        Green_LED.high()
        Blue_LED.high()
        
        LoadCellOffset = LoadCell.read_avg() #is essential for accurately calibrating the load cell sensor and ensuring that subsequent weight measurements are correct by accounting for any zero errors or noise in the sensor's readings.
        if Debug:
            print("Enter the Load")
        #aqua light: which mean you have to put the load( for now we have taken 5KG load for reference).
        Red_LED.low()
        Green_LED.high()
        Blue_LED.high()
        
        while LoadCell.read()-LoadCellOffset<2000: #. The system waits for the load cell to detect a significant change (more than 2000 units from the offset) to ensure the weight is placed correctly.
            print(LoadCell.read()-LoadCellOffset) 
            pass
        sleep(3)
        if Debug:
            print("Calibration Done")
        #magenta light load detected.
        Red_LED.high()
        Green_LED.low()
        Blue_LED.high()
        referenceLoad = LoadCell.read_avg()
        LoadCellScaling = referenceLoad- LoadCellOffset
        #calibration completed
        return [int(LoadCellScaling),int(LoadCellOffset)]
    except Exception as e:
        if Debug:
            print(e)

# Function to start the calibration
# This function also checks if calibration has already been done
def CalibrationStart():
    global LoadCellOffset,LoadCellScaling
    try:
        #it will check if calibration file exist with conaitning the calibration values
        if Start_Calibration_Flag!=1:
            calibrationFile = open('calibration.txt', "r")
            LoadCellValue = calibrationFile.read().split(',')  #2932, 41906
            if len(LoadCellValue)<=0:
                raise Exception("Blank File!!!")
            LoadCellScaling = int(LoadCellValue[0])
            LoadCellOffset = int(LoadCellValue[1])
            calibrationFile.close()
        else:
            raise Exception("Start Calibration!!!")
    except:
        #if not exist then it will calll the calibration function
        LoadArray = LoadSensorCalibration()
        calibrationFile = open('calibration.txt', "w")
        calibrationFile.write(str(LoadArray)[1:-1])
        calibrationFile.close()
        calibrationFile = open('calibration.txt', "r")
        LoadCellValue = calibrationFile.read().split(',')
        LoadCellScaling = int(LoadCellValue[0])
        LoadCellOffset = int(LoadCellValue[1])
        calibrationFile.close()

#Function to setup the gpio's and initialisation of the variables
def setup_gpio():
    global LA_Forward,LA_Feedback,BinLevelSensor_2,BinLevelSensor_1,Red_LED,Green_LED,Blue_LED,Pico_led,Registered_card,PWR_Key,reader,gsm,LM35_Sens,Pico_temp
    global conversion_factor,ProximitySensor,LA_Backward,Alert_code_buf,Bin_location,Garbage_limit,card
    global Debug,RFID_Card_State,Mosfet_1,Mosfet_2,LoadCell,MachineID,Calibration_card,Bin_Request_q,Post_Request_q
    global GPS
    GPS = True
    Debug = False
    # --------------pin defination------------------------
    Pico_led_pin =25
    # Pin for the bin level sensor
    Echo_pin_1 = 0
    Echo_pin_2 = 1
    # pin 2 for relay 2
    LA_Forward_pin = 2
    LA_Backward_pin = 3
    #pin 4(Rx) - tx GSM
    Pico_temp_pin= 4 # this is internal Analog pin not on board
    # pin 5 (Tx) - Rx GSM
    Power_key_pin = 6
    Bin_1_Mosfet_pin = 7
    LoadCell_SCK_pin = 8
    LoadCell_DOUT_pin = 9

    Blue_LED_pin = 12 # 12
    Green_LED_pin = 13
    Red_LED_pin = 14 #14
    # pin 16 MISO
    # pin 17 CS
    # pin 18 SCK
    # pin 19 MOSI
    # pin 20 RST
    ProximitySensor_pin = 21
    Bin_2_Mosfet_pin = 22
    LM35_Sens_pin = 2 # this is analog 2 and pin 28
    LA_Feedback_Pin = 26

    #--------------------------------------------------
    #-----------------object create--------------------
    reader = MFRC522(spi_id=0,sck=18,miso=16,mosi=19,cs=17,rst=20)
    gsm = GSM()
    Bin_Request_q = Queue()
    Post_Request_q = Queue()
    LM35_Sens = machine.ADC(LM35_Sens_pin)#object created for the LM35 sensor
    Pico_temp = machine.ADC(Pico_temp_pin)#object created for the pico temperature sensor
    
    LM35_Sens = machine.ADC(LM35_Sens_pin)#object created for the LM35 sensor
    Pico_temp = machine.ADC(Pico_temp_pin)#object created for the pico temperature sensor

    Pico_led = Pin(Pico_led_pin, Pin.OUT)#object created for the pico led
    BinLevelSensor_1 = Pin(Echo_pin_1, Pin.IN)#object created for the trash level -1  sensor
    BinLevelSensor_2 = Pin(Echo_pin_2, Pin.IN)#object created for the trash level -2  sensor
    Red_LED = Pin(Red_LED_pin, Pin.OUT)#object created for the Red LED
    Green_LED = Pin(Green_LED_pin, Pin.OUT)#object created for the Green LED
    Blue_LED = Pin(Blue_LED_pin, Pin.OUT)#object created for the Blue LED
    LA_Forward = Pin(LA_Forward_pin, Pin.OUT) # object for linear actuator in forward direction
    LA_Backward = Pin(LA_Backward_pin, Pin.OUT) # object for linear actuator in Backward direction
    LA_Feedback = machine.ADC(LA_Feedback_Pin)
    Mosfet_1 = Pin(Bin_1_Mosfet_pin, Pin.OUT) # object for Mosfet of bin 1 control
    Mosfet_2 = Pin(Bin_2_Mosfet_pin, Pin.OUT) # object for Mosfet of the bin 2 control
    # need to add one more relay and battery adc  and linear actuator feedback (Analog read)
    PWR_Key = Pin(Power_key_pin, Pin.OUT)# object for gsm power key
    
    ProximitySensor = Pin(ProximitySensor_pin, Pin.IN,Pin.PULL_UP) # object created for the Left Limit switch
    #RSwitch = Pin(RSwitch_pin, Pin.IN,Pin.PULL_UP) # object created for the Right Limit switch
    
    # object created for the Load cell pin
    LoadCellDout = Pin(LoadCell_DOUT_pin, Pin.IN, pull=Pin.PULL_DOWN) 
    LoadCellSck = Pin(LoadCell_SCK_pin, Pin.OUT)

    LoadCell = HX711(LoadCellSck, LoadCellDout)
    #----------initialization of the Led-------------------------
    Red_LED.low()
    Green_LED.high()
    Blue_LED.low()
    #--------------------------------------------------------


    #---------------variable initalisation-------------------------
    CalibrationStart()
    Alert_code_buf = [0,0,0,0,0]
    
    #Alert_code_buf[0] = TBL (TBL>90 ==> 1, TBL<90 ==> 0)
    #Alert_code_buf[1] = TBL Sensor (TBL working =0, Not working = 1)
    #Alert_code_buf[2] = RFID Sensor (RFID Sensor Working=0, RFID sensor not working =1)
    #Alert_code_buf[3] = Trash bin temperature (If temperature >75 then 1 else 0)
    #Alert_code_buf[4] = Proximity Switch status (if any one of two limit switch off then 0 other wise 1)
    #Alert_code_buf[5] = Trash bin Weight Value( if Trash bin Weight return the 0, then 1 else 0)
    
    conversion_factor = 3.3/65535 # conversion factor for the temperature
    #This variable contains the id of the cards i.e registration and calibration cards
    Registered_card = [1406550997,821727780,810275380,818998548,2867844179,2840751363,269220211,2866838851] #registered card for the SWBMS
    Calibration_card = [1394027849,2863046499,2862073619,2863075139,2846364563]
    Garbage_limit = 40
    card=0
    Bin_location = {"latitude": 0,"longitude": 0}
    RFID_Card_State = False

    MachineID = ubinascii.hexlify(machine.unique_id()).decode() # get the MAC ID/device id
    Mosfet_1.high()
    Mosfet_2.high()
    Pico_led.high()

#function perform for pico led on
def led_on():
    Pico_led.value(1)
#function perform for pico led off
def led_off():
    Pico_led.value(0) 

#function to send the data on tadweer api and also get the data for the qr scan
def tadweerApi(Bin_Request_q,Post_Request_q):
    inital_value = ''
    while True:
        try:
            if Post_Request_q.empty() and Bin_Request_q.empty():
                get_init_resp = gsm.send_at("AT+HTTPINIT",timeout=10000)
                if "ERROR" in get_init_resp:
                    gsm.send_at("AT+HTTPTERM",timeout=120000)
                    gsm.send_at("AT+HTTPINIT",timeout=10000)
                gsm.send_at('AT+HTTPPARA=\"URL\",\"'+get_url+MachineID+'\"',timeout=10000) 
                gsm.send_at("AT+HTTPACTION=0", timeout=15000,check=b'200')
                rcv = gsm.send_at('AT+HTTPREAD=0,100',timeout=15000,check=b'}')
                if "track_id" in rcv:
                    rcv = rcv.replace(" ", "").replace("\n", "")
                    rcv = ure.search('({.*})', rcv).group(1)
                    try:
                        Packet = json.loads(rcv)
                        RandomString = Packet.get("track_id")
                        if inital_value != RandomString:
                            user_id = Packet.get("user_id")
                            Bin_Request_q._put(user_id)
                            inital_value=RandomString
                    except Exception as e:
                        if Debug:
                            print("Error : ", e)
                        pass            
                gsm.send_at("AT+HTTPTERM",timeout=120000)
            elif not(Post_Request_q.empty()):
                try:
                    data_json = Post_Request_q._get()
                except:
                    pass
                #if gsm.check_network():
                http_str = 'AT+HTTPPARA=\"URL\",\"'+post_url+'\"';
                post_init_resp = gsm.send_at("AT+HTTPINIT",timeout=10000)  # ,check=True)
                if "ERROR" in post_init_resp:
                    gsm.send_at("AT+HTTPTERM",timeout=120000)  # ,check=True)
                    gsm.send_at("AT+HTTPINIT",timeout=10000)#
                gsm.send_at("AT+HTTPINIT",timeout=10000)
                gsm.send_at(http_str,timeout=10000)                   
                gsm.send_at("AT+HTTPPARA=\"CONTENT\",\"application/json\"",timeout=10000)
                gsm.send_at("AT+HTTPDATA="+str(len(data_json))+",10000",timeout=10000)
                gsm.send_at(data_json,timeout=10000)
                gsm.send_at("AT+HTTPACTION=1", timeout=15000,check=b'200')
                gsm.send_at("AT+HTTPTERM",timeout=120000)
                    #print("Sent")
            else:
                if Debug:
                    print("NO GET POST")
                pass
        except Exception as e:
            if Debug:
                print("Error : ",e)
            #print("error",e)
            pass
#This function is utilized to monitor for any potential faults in the Bin level sensor
def TBL_check():
    myStartingTime = time()
    global Alert_code_buf,Garbage_limit
    Mosfet_1.high()
    Mosfet_2.high()
    signalon = ticks_us()
    signaloff = ticks_us()
    #print("Test Signal difference: -",signalon-signaloff)
    while BinLevelSensor_2.value() == 0:
        # this is to check the Sensor Connected or not
        calculation_starting_time = time()
        if calculation_starting_time - myStartingTime > 1:
            break
        signaloff = ticks_us()

    while BinLevelSensor_2.value() == 1:
        # this is to check the Sensor Connected or not
        calculation_starting_time = time()
        if calculation_starting_time - myStartingTime > 1:
            break
        signalon = ticks_us()

    timepassed = signalon - signaloff
    distance = (timepassed)/58
    if timepassed<0:
        Alert_code_buf[1] = 1
        return -1
    else:
        Alert_code_buf[1] = 0
        return distance
#This function perform the bin level meaurement and also update teh alert code
def bin_level(timeout_seconds=5):
    global Alert_code_buf, Garbage_limit
    Mosfet_1.high()
    Mosfet_2.high()
    Average_distance = []
    start_time = ticks_ms()

    for i in range(0, 5):
        signalon = ticks_us()
        signaloff = ticks_us()

        while BinLevelSensor_2.value() == 0:
            signaloff = ticks_us()
            if time.ticks_diff(ticks_ms(), start_time) / 1000 > timeout_seconds:
                print("Timeout reached. Exiting loop.")
                break

        while BinLevelSensor_2.value() == 1:
            signalon = ticks_us()
            if time.ticks_diff(ticks_ms(), start_time) / 1000 > timeout_seconds:
                print("Timeout reached. Exiting loop.")
                break

        timepassed = signalon - signaloff
        distance = (timepassed) / 58
        Average_distance.append(distance)

    distance = sum(Average_distance) / 5

    if distance <= Garbage_limit:
        Alert_code_buf[0] = 1
    else:
        Alert_code_buf[0] = 0

    return distance

#function to blink the blue led    
def Blink_Blue(blinks):
    Red_LED.low()
    Green_LED.low()
    Blue_LED.low()
    for i in range(0, blinks):
        Blue_LED.low()
        sleep(0.25)
        Blue_LED.high()
        sleep(0.25)

# function to setup gsm
def setup_gsm():  # function initializes the GSM module, configures it, checks for network connectivity, and provides visual feedback using LEDs.
    Red_LED.high()
    Green_LED.low()
    Blue_LED.low()
    # Start the gsm
    if gsm.boot_gsm():# This method tries to power on the GSM module and checks if it's ready to use by sending the "AT" command up to 20 times. If it gets an "OK" response, it means the module is ready.
        gsm.configure_gsm() #This method sets up the GSM module for operations by: @ Deleting all stored messages. @ Setting the network mode to automatic (finds the best available network).@ Setting up an APN (Access Point Name) for internet access.
        # Check we're attached
        state = True
        while not gsm.check_network():
            #print("trut")
            if state:
                led_on()
                #LED_R.high()
            else:
                led_off()
                #LED_R.low()
            state = not state
        # Light the LED
        led_on()
    else:
        # Error! Blink LED 5 times
        Blink_Blue(5)
        led_off()
        
#function to initalise the GPS
def setup_gps():
    gsm.send_at('AT+CGNSSPWR=0,0,0',timeout=10000) #  This command turns off the GPS power to reset the module or ensure it starts from a known state.
    gsm.send_at("AT+CGNSSPWR=1",timeout=10000)    #This command powers on the GPS module. It is essential for enabling the GPS hardware to begin satellite communication for location tracking.
    gsm.send_at("AT+CGNSSMODE=1",timeout=10000)  #Configures the mode in which the GPS operates.
    gsm.send_at("AT+CGPSCOLD",timeout=10000)    #Initializes the GPS module to perform a "cold start."
    gsm.send_at("AT+CAGPS",timeout=10000)      # This command appears to be an additional GPS configuration or initialization command. The exact behavior might vary depending on the GSM/GPS module model.

#Function to extract the gps value from the Raw GPS packet
def extract_coordinates(string):
    pattern = r'(\d+\.\d+),([NSEW]),(\d+\.\d+),([NSEW])'
    match = ure.search(pattern, string)
    
    if match:
        latitude_decimal = float(match.group(1))
        if match.group(2) in ('S', 'W'):
            latitude_decimal *= -1
        
        longitude_decimal = float(match.group(3))
        if match.group(4) in ('S', 'W'):
            longitude_decimal *= -1
        
        return latitude_decimal, longitude_decimal
    else:
        return None

#Function to convert the raw gps value into degree minute
def convert_coordinates(latitude, longitude):
    latitude_degrees = int(latitude) // 100
    latitude_minutes = float(latitude) % 100 / 60
    latitude_decimal = latitude_degrees + latitude_minutes

    longitude_degrees = int(longitude) // 100
    longitude_minutes = float(longitude) % 100 / 60
    longitude_decimal = longitude_degrees + longitude_minutes

    return latitude_decimal, longitude_decimal    


#Function to update the Bin location Packet
def fetch_gps_location():
    global Bin_location
    response = gsm.send_at("AT+CGPSINFO",timeout=10000)
    coordinates = extract_coordinates(response)
    if coordinates:
        latitude_str, longitude_str = coordinates
        latitude, longitude = convert_coordinates(latitude_str, longitude_str)
        Bin_location['latitude'] = str(latitude)
        Bin_location['longitude'] = str(longitude)

#function to get the bin temperature from lm35 and pico
def bin_temperature():   #Reads temperature from the LM35 sensor and the built-in Pico temperature sensor.  Calculates the average temperature and raises alerts if it exceeds a certain threshold.

    global Alert_code_buf
    lm35_temp_voltage_raw = LM35_Sens.read_u16()*conversion_factor
    pico_temp_voltage_raw = Pico_temp.read_u16()* conversion_factor
    # Converts ADC value back to voltage
    pico_temperature = 27 - (pico_temp_voltage_raw - 0.706)/0.001721
    lm35_temperature = lm35_temp_voltage_raw/(10.0 / 1000)
    #F_temp = (C_temp*9/5) +32
    Average_temperature = (pico_temperature+lm35_temperature)/2
    #print(pico_temperature,lm35_temperature,Average_temperature)
    if Average_temperature>80:
        Alert_code_buf[3] = 1
    return Average_temperature

#function to check the bin lid
def check_Bin_Lid():
    global Alert_code_buf
    if ProximitySensor.value()==1:
        Alert_code_buf[4] = 0
        return 1
    else:
        Alert_code_buf[4] = 1
        return 0


# function to read the Bin weight
def Bin_Weight():
    try:
        TBW = ((LoadCell.read()-LoadCellOffset)/LoadCellScaling)*4.9
        if TBW>-1 and TBW<0:
            TBW =0
        #Alert_code_buf[5] = 0
        return TBW-37 #37 is the main bin weight which is inside the container or bin
    except Exception as e:
        #print(e)
        #Alert_code_buf[5] = 1
        return 0
    
# function to check the bin ideal state 

def Bin_ideal_check():
    global Alert_code_buf
    TBL_check()       # This function likely checks the bin level sensor, possibly using an ultrasonic sensor to determine the trash level in the bin. It updates
    check_Bin_Lid()   # This function checks the status of the bin lid, possibly using a proximity sensor or limit switch to determine if the lid is open or closed. It updates
    bin_temperature() #This function reads the temperature inside the bin using a temperature sensor (e.g., LM35 or onboard Pico temperature sensor) and updates
    Bin_Weight()      #: This function measures the weight of the trash in the bin using a load cell and updates Alert_code_buf[5] based on the weight reading.
    try:
        reader.init()
        #print(reader.check())
        if reader.check()==0: 
            Alert_code_buf[2] = 1
        else:
            Alert_code_buf[2] = 0
    except:
        Alert_code_buf[2] = 1

# This loop will execute continuously, provided all functions run without errors
while True:                     # This main loop runs indefinitely to continuously monitor and manage the waste bin system.
    try:
        setup_gpio()            # Setting up all input and output pins
        led_off()               # Turning off the LED indicator
        setup_gsm()             # Boots and configures the GSM module to ensure it's ready for communication. It checks for network connectivity and handles any errors that occur during initialization.
        setup_gps()             # Powers on the GPS module, configures it for location tracking, and prepares it to fetch GPS data.
        Bin_ideal_check()       # Runs a series of diagnostic checks on the bin's sensors and components (e.g., RFID reader, bin level sensor, lid sensor, temperature sensor, load cell). Updates the Alert_code_buf based on the results.
        myStartingTime = time() 
        while True:             # This inner loop attempts to fetch the GPS location of the bin for up to 60 seconds.
            #calculation_starting_time = 
            if time() - myStartingTime > 60:
                break
            try:
                fetch_gps_location()
            except:
                pass
        Bin_Data_Packet ={"tbmID":MachineID,"user_id":0,"tbt":int(bin_temperature()),"tbl":int(TBL_check()),"tbw":Bin_Weight(),"latitude":Bin_location['latitude'],"longitude":Bin_location['longitude'],"alerts":str(Alert_code_buf)[1:-1]}
        Bin_Data_Packet = json.dumps(Bin_Data_Packet)
        Post_Request_q._put(Bin_Data_Packet)
        if Debug:
            print(Alert_code_buf)
        break
    except:
        pass
# Start a thread to handle data sending and fetching processes using a queue management system
# This system facilitates communication between two core processes
_thread.start_new_thread(tadweerApi, (Bin_Request_q,Post_Request_q,))  

# This loop will run continuously in the beginning if any alert code occurs.
# It will also update the queue to send that data over the server/api
while 1 in Alert_code_buf:
    try:
        Bin_ideal_check()
        Red_LED.high()
        Green_LED.low()
        Blue_LED.low()
        #print(Alert_code_buf)
        TBL_check()
        #fetch_gps_location()
        Bin_Data_Packet ={"tbmID":MachineID,"user_id":card,"tbt":int(bin_temperature()),"tbl":int(TBL_check()),"tbw":Bin_Weight(),"latitude":Bin_location['latitude'],"longitude":Bin_location['longitude'],"alerts":str(Alert_code_buf)[1:-1]}
        #Bin_Data_Packet = {"ID":0,"TBT":int(bin_temperature()),"TBL":int(23),"TBW":23,"TBGL":Bin_location}
        Bin_Data_Packet = json.dumps(Bin_Data_Packet)
        Post_Request_q._put(Bin_Data_Packet)
    except:
        pass

# This loop, running in core 1, continuously monitors the bin parameters.
while True:
    try:
        space = TBL_check()             #signalon - signaloff/58 or -1
        temperature = bin_temperature() # Average_temperature
        TBW = Bin_Weight()              # TBW-37
        check_Bin_Lid()
        try:
            reader.init()              # Initializes the RFID reader, setting up necessary configurations to make it ready for operation. This might include SPI communication setup and powering up the antenna.
            if reader.check()==0:      # Check the version of the RFID reader to ensure it's working correctly   Checks if the RFID reader is responding correctly. The method check() returns the version number or status. A return value of 0 might indicate a failure or malfunction. If so, an alert is set in Alert_code_buf[2]
                Alert_code_buf[2] = 1
            else:
                Alert_code_buf[2] = 0
            (stat, tag_type) = reader.request(reader.REQIDL)   # |
            if stat == reader.OK:                              # | The RFID reader attempts to detect a tag using reader.request(reader.REQIDL) and selects a tag using reader.SelectTagSN().
                (stat, uid) = reader.SelectTagSN()             # |
                if stat == reader.OK:
                    card = int.from_bytes(bytes(uid),"little",False)
                    RFID_Card_State = True
                    if Debug:
                        print("CARD ID: "+str(card))
                    #RFID_flag =True
        except Exception as E:
            if Debug:
                print("RFID exception",E)        
        if 1 in Alert_code_buf:
            #RED LED Glow if any bit is high in alert_code_buf
            Red_LED.high()
            Green_LED.low()
            Blue_LED.low()
            Bin_Data_Packet = {"tbmID":MachineID,"user_id":0,"tbt":int(bin_temperature()),"tbl":int(TBL_check()),"tbw":Bin_Weight(),"latitude":Bin_location['latitude'],"longitude":Bin_location['longitude'],"alerts":str(Alert_code_buf)[1:-1]}
            Bin_Data_Packet = json.dumps(Bin_Data_Packet)
            Post_Request_q._put(Bin_Data_Packet)
        else:
            try:
                #initialise and check the reader
                if (RFID_Card_State == True) or (not Bin_Request_q.empty()): # If an RFID card is detected or there is a request in Bin_Request_q, the system checks if the card is registered.
                    try:
                        QR_request = Bin_Request_q._get()
                        card = QR_request
                        QR_request = True
                    except:
                        QR_request = False
                        #pass
                    if (card in Registered_card) or QR_request: #and RFID_flag == True: If an RFID card is detected or there is a request in Bin_Request_q, the system checks if the card is registered.
                        #BLue LED Glow
                        Red_LED.low()
                        Green_LED.low()
                        Blue_LED.high()
                        
                        sleep(0.5) #delay
                        
                        # Linear Actuator Backward
                        LA_Forward.high() #unlock the bin
                        LA_Backward.low()
                        sleep(2)
                        
                        #Stop the Linear Actuator
                        LA_Forward.low()
                        LA_Backward.low()
                        
                        #Yellow LED Glow
                        Red_LED.high()
                        Green_LED.high()
                        Blue_LED.low()
                        sleep(3) #delay
                        start_time = time()
                        
                        while ProximitySensor.value()==1:#Wait for the bin Open:
                            if time()- start_time > 120:
                                break

                        start_time = time()
                        while not(ProximitySensor.value()==1):#Wait for the bin close:                        
                            if time()- start_time > 120:
                                #update the Bin_data_packet to send the alert code and other parameter on server
                                Bin_Data_Packet = {"tbmID":MachineID,"user_id":card,"tbt":int(temperature),"tbl":int(bin_level()),"tbw":Bin_Weight(),"latitude":Bin_location['latitude'],"longitude":Bin_location['longitude'],"alerts":"0,0,0,0,1,0"}
                                Bin_Data_Packet = json.dumps(Bin_Data_Packet)
                                Post_Request_q._put(Bin_Data_Packet)
                                #Red LED Glow
                                Red_LED.high()
                                Green_LED.low()
                                Blue_LED.low()
                                start_time = time()                        
                        #Blue LED Glow
                        Red_LED.low()
                        Green_LED.low()
                        Blue_LED.high()
                        sleep(1)
                        #lock the bin
                        LA_Forward.low() 
                        LA_Backward.high()
                        sleep(2)
                        LA_Forward.low()
                        LA_Backward.low()   
                        RFID_Card_State = False
                        # update the Queue buffer
                        Bin_Data_Packet ={"tbmID":MachineID,"user_id":card,"tbt":int(temperature),"tbl":int(bin_level()),"tbw":Bin_Weight(),"latitude":Bin_location['latitude'],"longitude":Bin_location['longitude'],"alerts":"0,0,0,0,0"}
                        Bin_Data_Packet = json.dumps(Bin_Data_Packet)
                        Post_Request_q._put(Bin_Data_Packet)
                        card = 0

                        '''
                           If the card is valid:
                                    The blue LED is turned on.
                                    A linear actuator is triggered to unlock the bin (move forward).
                                    The yellow LED is turned on to indicate the bin is ready to use.
                                    The system waits for the bin lid to be opened (checked using a proximity sensor).
                                    Once the bin lid is closed, the bin is locked again using the actuator.
                        '''
                    elif card in Calibration_card:  #Handling Calibration Cards: @ If a calibration card is detected, the system starts the calibration process using CalibrationStart()
                        #if clibration_card
                        CalibrationStart()
                        Start_Calibration_Flag = 1
                        card = 0
                    else:
                        RFID_Card_State = False
                        Blink_Blue(3)                
                else:
                    #Green LED glow if no alert code or bin not in use.
                    Red_LED.low()
                    Green_LED.high()
                    Blue_LED.low()                         
            except Exception as e:
                if Debug:
                    print(e)
                #if any exception occurs RED LED GLOW
                Red_LED.high()
                Green_LED.low()
                Blue_LED.low()
    except Exception as e:
        if Debug:
            print(e)
        #if any exception occurs RED LED GLOW
        Red_LED.high()
        Green_LED.low()
        Blue_LED.low()