import time
import network
import urequests
import machine
import ujson
import dht
from onewire import OneWire
from ds18x20 import DS18X20
import neopixel
import bme280
from machine import ADC, Pin

# ===========================
# Configuration and Constants
# ===========================

# ThingSpeak settings
THINGSPEAK_API_KEY = "7BGA6EMJC7QLM8HB"
THINGSPEAK_URL = "https://api.thingspeak.com/update?api_key=7BGA6EMJC7QLM8HB&field1=0"

# Wi-Fi settings
WIFI_SSID = "iPhone Little"
WIFI_PASSWORD = "87654321"

# Sensor Pins
DHT_PIN = 4        # GPIO4 for DHT22
DS18B20_PIN = 5    # GPIO5 for DS18B20
NEOPIXEL_PIN = 2   # GPIO2 for NeoPixel
I2C_SCL_PIN = 14   # GPIO14 for BME280 SCL
I2C_SDA_PIN = 15   # GPIO15 for BME280 SDA
UV_SENSOR_PIN = 26  # GPIO26 for UV Sensor (Analog)

# NeoPixel configuration
NEOPIXEL_COUNT = 16  # Number of NeoPixels

# ThingSpeak rate limit (in seconds)
THINGSPEAK_DELAY = 15

# ===========================
# Initialization
# ===========================

# Initialize DHT22 sensor
dht_sensor = dht.DHT22(Pin(DHT_PIN))

# Initialize DS18B20 sensor
ow = OneWire(Pin(DS18B20_PIN))
ds_sensor = DS18X20(ow)
ds_roms = ds_sensor.scan()
if not ds_roms:
    print("Warning: No DS18B20 devices found.")

# Initialize NeoPixel
np = neopixel.NeoPixel(Pin(NEOPIXEL_PIN), NEOPIXEL_COUNT)

# Initialize BME280 sensor
try:
    i2c = machine.I2C(0, scl=Pin(I2C_SCL_PIN), sda=Pin(I2C_SDA_PIN))
    bme = bme280.BME280(i2c=i2c)
except Exception as e:
    print("Error initializing BME280:", e)
    bme = None

# Initialize UV Sensor
try:
    uv_sensor = ADC(Pin(UV_SENSOR_PIN))
    uv_sensor.atten(ADC.ATTN_11DB)  # Configure attenuation for wider input range
except Exception as e:
    print("Error initializing UV sensor:", e)
    uv_sensor = None

# ===========================
# Utility Functions
# ===========================

def connect_wifi(retries=20, delay=2):
    """
    Connect to the configured Wi-Fi network.

    :param retries: Number of connection attempts
    :param delay: Delay between attempts in seconds
    :return: True if connected, False otherwise
    """
    wifi = network.WLAN(network.STA_IF)
    wifi.active(True)
    if not wifi.isconnected():
        print("Connecting to Wi-Fi...")
        wifi.connect(WIFI_SSID, WIFI_PASSWORD)
        attempt = 0
        while not wifi.isconnected() and attempt < retries:
            print(f"Attempt {attempt + 1}/{retries}...")
            time.sleep(delay)
            attempt += 1
    if wifi.isconnected():
        print("Connected to Wi-Fi")
        print("IP Address:", wifi.ifconfig()[0])
        return True
    else:
        print("Failed to connect to Wi-Fi")
        return False

def set_neopixel_color(uv_intensity):
    """
    Set NeoPixel colors based on UV intensity.

    :param uv_intensity: UV intensity percentage (0-100)
    """
    if uv_intensity is None:
        color = (0, 0, 0)  # Turn off NeoPixels if UV level is unavailable
    elif uv_intensity > 75:
        color = (255, 0, 0)  # Red for high intensity
    elif uv_intensity > 25:
        color = (255, 255, 0)  # Yellow for medium intensity
    else:
        color = (0, 255, 0)  # Green for low intensity
    
    for i in range(NEOPIXEL_COUNT):
        np[i] = color
    np.write()

def read_dht22():
    """
    Read temperature and humidity from DHT22 sensor.

    :return: Tuple of (temperature, humidity) or (None, None) on failure
    """
    try:
        dht_sensor.measure()
        temperature = dht_sensor.temperature()
        humidity = dht_sensor.humidity()
        return temperature, humidity
    except OSError as e:
        print("DHT22 read error:", e)
        return None, None

def read_ds18b20():
    """
    Read temperature from DS18B20 sensor.

    :return: Soil temperature or None on failure
    """
    if not ds_roms:
        return None
    try:
        ds_sensor.convert_temp()
        time.sleep(1)  # Wait for conversion
        soil_temp = ds_sensor.read_temp(ds_roms[0])
        return soil_temp
    except Exception as e:
        print("DS18B20 read error:", e)
        return None

def read_bme280():
    """
    Read pressure from BME280 sensor.

    :return: Pressure in hPa or None on failure
    """
    if not bme:
        return None
    try:
        pressure = bme.pressure
        return pressure
    except Exception as e:
        print("BME280 read error:", e)
        return None

def read_uv_sensor():
    """
    Read UV level from UV sensor.

    :return: UV level percentage (0-100) or None on failure
    """
    if not uv_sensor:
        return None
    try:
        uv_value = uv_sensor.read_u16()  # Read 16-bit ADC value
        uv_percentage = (uv_value / 65535) * 100  # Convert to percentage
        return uv_percentage
    except Exception as e:
        print("UV sensor read error:", e)
        return None

def send_thingspeak(temperature, humidity, soil_temp, pressure, uv_level):
    """
    Send sensor data to ThingSpeak.

    :param temperature: Room temperature
    :param humidity: Room humidity
    :param soil_temp: Soil temperature
    :param pressure: Atmospheric pressure
    :param uv_level: UV intensity level
    """
    try:
        # Prepare URL-encoded data
        data = {
            "api_key": THINGSPEAK_API_KEY
        }
        if temperature is not None:
            data["field1"] = temperature
        if humidity is not None:
            data["field2"] = humidity
        if soil_temp is not None:
            data["field3"] = soil_temp
        if pressure is not None:
            data["field4"] = pressure
        if uv_level is not None:
            data["field5"] = uv_level
        
        # Encode data for application/x-www-form-urlencoded
        encoded_data = "&".join([f"{key}={value}" for key, value in data.items()])
        
        headers = {"Content-Type": "application/x-www-form-urlencoded"}
        response = urequests.post(THINGSPEAK_URL, data=encoded_data, headers=headers)
        if response.status_code == 200:
            print("Data successfully sent to ThingSpeak.")
        else:
            print(f"ThingSpeak HTTP Error: {response.status_code}")
        response.close()
    except Exception as e:
        print("Error sending data to ThingSpeak:", e)

# ===========================
# Main Execution Loop
# ===========================

def main():
    # Connect to Wi-Fi
    if not connect_wifi():
        print("Exiting due to Wi-Fi connection failure.")
        return

    data_counter = 0  # Initialize data sent counter

    try:
        while True:
            # Read sensor data
            temperature, humidity = read_dht22()
            soil_temp = read_ds18b20()
            pressure = read_bme280()
            uv_level = read_uv_sensor()

            # Display sensor readings
            print(f"Temperature: {temperature}°C" if temperature is not None else "Temperature: N/A")
            print(f"Humidity: {humidity}%" if humidity is not None else "Humidity: N/A")
            print(f"Soil Temperature: {soil_temp}°C" if soil_temp is not None else "Soil Temperature: N/A")
            print(f"Pressure: {pressure} hPa" if pressure is not None else "Pressure: N/A")
            print(f"UV Level: {uv_level}%" if uv_level is not None else "UV Level: N/A")

            # Update NeoPixel based on UV intensity
            set_neopixel_color(uv_level)

            # Send data to ThingSpeak
            send_thingspeak(temperature, humidity, soil_temp, pressure, uv_level)
            data_counter += 1
            print(f"Data transmission count: {data_counter}")

            # Wait for the specified delay
            time.sleep(THINGSPEAK_DELAY)
    except KeyboardInterrupt:
        print("Program terminated by user.")
    except Exception as e:
        print("An unexpected error occurred:", e)

if __name__ == "__main__":
    main()
Error initializing BME280: bad SCL pin
Error initializing UV sensor: 'ADC' object has no attribute 'atten'