# Filename: main.py

import time
import network
import urequests
import machine
import math
from machine import Pin, ADC
from onewire import OneWire
from neopixel import NeoPixel

# Import sensor modules
import dht
from ds18x20 import DS18X20
import bme280_float  # Using a float version for better precision (if available)
# import bme280  # Use this if bme280_float is not available

# Import configuration
try:
    from config import WIFI_SSID, WIFI_PASSWORD, THINGSPEAK_API_KEY
except ImportError:
    WIFI_SSID = 'your_ssid'
    WIFI_PASSWORD = 'your_password'
    THINGSPEAK_API_KEY = 'your_api_key'

# Configuration and Constants
THINGSPEAK_URL = 'https://api.thingspeak.com/update'
THINGSPEAK_DELAY = 1  # ThingSpeak free account limit: 1 update every 15 seconds

# Pin Definitions
DHT_PIN = 4
DS18B20_PIN = 5
NEOPIXEL_PIN = 2
I2C_SCL_PIN = 15
I2C_SDA_PIN = 14
UV_SENSOR_PIN = 28

NEOPIXEL_COUNT = 12
UV_LED_RANGE = range(0, 6)
TEMP_LED_RANGE = range(6, 12)

# Color Definitions
COLOR_RED = (255, 0, 0)
COLOR_YELLOW = (255, 255, 0)
COLOR_GREEN = (0, 255, 0)
COLOR_BLUE = (0, 0, 255)
COLOR_MAGENTA = (255, 0, 255)
COLOR_BLACK = (0, 0, 0)

# Sensor Manager Class
class SensorManager:
    def __init__(self):
        # Initialize sensors
        self.dht_sensor = dht.DHT22(Pin(DHT_PIN))
        ow = OneWire(Pin(DS18B20_PIN))
        self.ds_sensor = DS18X20(ow)
        self.ds_roms = self.ds_sensor.scan()
        if not self.ds_roms:
            print("Warning: No DS18B20 devices found.")
        try:
            self.i2c = machine.I2C(1, scl=Pin(I2C_SCL_PIN), sda=Pin(I2C_SDA_PIN), freq=400000)
            self.bme = bme280_float.BME280(i2c=self.i2c)
            # self.bme = bme280.BME280(i2c=self.i2c)  # Use this if float version is not available
        except Exception as e:
            print("Error initializing BME280:", e)
            self.bme = None
        try:
            self.uv_sensor = ADC(Pin(UV_SENSOR_PIN))
            print("UV sensor initialized.")
        except Exception as e:
            print("Error initializing UV sensor:", e)
            self.uv_sensor = None

    def read_dht22(self):
        """Read temperature and humidity from DHT22 sensor."""
        try:
            self.dht_sensor.measure()
            temperature = self.dht_sensor.temperature()
            humidity = self.dht_sensor.humidity()
            return temperature, humidity
        except OSError as e:
            print("DHT22 read error:", e)
            return None, None

    def read_ds18b20(self):
        """Read temperature from DS18B20 sensor."""
        if not self.ds_roms:
            return None
        try:
            self.ds_sensor.convert_temp()
            time.sleep_ms(750)  # DS18B20 requires a delay to perform the conversion
            soil_temp = self.ds_sensor.read_temp(self.ds_roms[0])
            return soil_temp
        except Exception as e:
            print("DS18B20 read error:", e)
            return None

    def read_bme280(self):
        """Read temperature, pressure, and humidity from BME280 sensor."""
        if not self.bme:
            return None, None, None
        try:
            temperature, pressure, humidity = self.bme.read_compensated_data()
            temperature = temperature  # Already in degrees Celsius
            pressure = pressure / 100  # Convert to hPa
            humidity = humidity  # Already in %
            return temperature, pressure, humidity
        except OSError as e:
            print("BME280 read error:", e)
            return None, None, None

    def read_uv_sensor(self):
        """Read UV level from UV sensor."""
        if not self.uv_sensor:
            return None
        try:
            uv_value = self.uv_sensor.read_u16()
            uv_percentage = (uv_value / 65535.0) * 100.0
            return uv_percentage
        except Exception as e:
            print("UV sensor read error:", e)
            return None

    def read_all_sensors(self):
        """Read all sensor data."""
        temperature_dht, humidity_dht = self.read_dht22()
        soil_temp = self.read_ds18b20()
        temperature_bme, pressure, humidity_bme = self.read_bme280()
        uv_level = self.read_uv_sensor()
        return {
            'temperature_dht': temperature_dht,
            'humidity_dht': humidity_dht,
            'soil_temp': soil_temp,
            'temperature_bme': temperature_bme,
            'pressure': pressure,
            'humidity_bme': humidity_bme,
            'uv_level': uv_level,
        }

# NeoPixel Manager Class
class NeoPixelManager:
    def __init__(self, pin_num, count):
        self.np = NeoPixel(Pin(pin_num), count)
        self.breathing_phase = 0
        self.last_update_time = time.ticks_ms()
        self.breathing_speed = 2000  # Breathing cycle duration in milliseconds

    def set_color(self, led_range, color):
        for i in led_range:
            self.np[i] = color
        self.np.write()

    def update_breathing(self, led_range, base_color):
        """Update the breathing effect without blocking."""
        current_time = time.ticks_ms()
        elapsed = time.ticks_diff(current_time, self.last_update_time)
        self.last_update_time = current_time

        self.breathing_phase += (elapsed / self.breathing_speed) * 2 * math.pi
        if self.breathing_phase > 2 * math.pi:
            self.breathing_phase -= 2 * math.pi

        factor = (math.sin(self.breathing_phase) + 1) / 2  # Normalize to [0, 1]
        dimmed_color = tuple(int(c * factor) for c in base_color)

        for i in led_range:
            self.np[i] = dimmed_color
        self.np.write()

    def clear(self):
        self.set_color(range(self.np.n), COLOR_BLACK)

# Wi-Fi Connection Function
def connect_wifi(ssid, password):
    wlan = network.WLAN(network.STA_IF)
    wlan.active(True)
    max_attempts = 20
    attempt = 0

    if not wlan.isconnected():
        print(f"Attempting to connect to SSID: {ssid}")
        wlan.connect(ssid, password)
        while not wlan.isconnected() and attempt < max_attempts:
            attempt += 1
            print(f"Attempt {attempt}/{max_attempts}...")
            time.sleep(1)
        if wlan.isconnected():
            print("Connected to Wi-Fi")
            print("Network Configuration:", wlan.ifconfig())
        else:
            print("Failed to connect to Wi-Fi.")
    else:
        print("Already connected to Wi-Fi.")

    # Additional connectivity check
    if wlan.isconnected():
        try:
            response = urequests.get("http://clients3.google.com/generate_204")
            if response.status_code == 204:
                print("Internet check successful.")
            else:
                print("Internet check failed.")
            response.close()
        except Exception as e:
            print("No Internet access:", e)
    return wlan.isconnected()

# ThingSpeak Data Transmission Function
def send_thingspeak(data):
    """Send sensor data to ThingSpeak."""
    try:
        payload = {
            "api_key": THINGSPEAK_API_KEY,
        }
        # Map data fields to ThingSpeak fields
        field_mapping = {
            'field1': data.get('temperature_dht'),
            'field2': data.get('humidity_dht'),
            'field3': data.get('soil_temp'),
            'field4': data.get('pressure'),
            'field5': data.get('uv_level'),
            'field6': data.get('temperature_bme'),
            'field7': data.get('humidity_bme'),
        }
        # Remove None values
        filtered_payload = {k: v for k, v in field_mapping.items() if v is not None}
        payload.update(filtered_payload)

        # Send HTTP POST request
        response = urequests.post(THINGSPEAK_URL, json=payload)
        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 Function
def main():
    # Connect to Wi-Fi
    connected = connect_wifi(WIFI_SSID, WIFI_PASSWORD)
    if not connected:
        raise RuntimeError("Cannot proceed without a Wi-Fi connection.")

    # Initialize Sensors
    sensor_manager = SensorManager()

    # Initialize NeoPixels
    neopixel_manager = NeoPixelManager(NEOPIXEL_PIN, NEOPIXEL_COUNT)

    last_thingspeak_update = time.time()
    data_counter = 0

    print("Starting data collection and transmission loop...")
    try:
        while True:
            # Read sensor data
            sensor_data = sensor_manager.read_all_sensors()

            # Display sensor readings
            print(f"--- Measurement {data_counter + 1} ---")
            print(f"Room Temperature (DHT22): {sensor_data['temperature_dht']}°C" if sensor_data['temperature_dht'] is not None else "Room Temperature (DHT22): N/A")
            print(f"Room Humidity (DHT22): {sensor_data['humidity_dht']}%" if sensor_data['humidity_dht'] is not None else "Room Humidity (DHT22): N/A")
            print(f"Soil Temperature (DS18B20): {sensor_data['soil_temp']}°C" if sensor_data['soil_temp'] is not None else "Soil Temperature (DS18B20): N/A")
            print(f"Atmospheric Pressure (BME280): {sensor_data['pressure']} hPa" if sensor_data['pressure'] is not None else "Atmospheric Pressure (BME280): N/A")
            print(f"Room Temperature (BME280): {sensor_data['temperature_bme']}°C" if sensor_data['temperature_bme'] is not None else "Room Temperature (BME280): N/A")
            print(f"Room Humidity (BME280): {sensor_data['humidity_bme']}%" if sensor_data['humidity_bme'] is not None else "Room Humidity (BME280): N/A")
            print(f"UV Intensity Level: {sensor_data['uv_level']}%" if sensor_data['uv_level'] is not None else "UV Intensity Level: N/A")

            # Update NeoPixel colors based on sensor readings without blocking
            uv_level = sensor_data['uv_level']
            temperature = sensor_data['temperature_dht'] if sensor_data['temperature_dht'] is not None else sensor_data['temperature_bme']

            # Determine UV color
            if uv_level is None:
                uv_color = COLOR_BLACK
            elif uv_level > 40:
                uv_color = COLOR_RED
            elif uv_level > 25:
                uv_color = COLOR_YELLOW
            else:
                uv_color = COLOR_MAGENTA
            neopixel_manager.update_breathing(UV_LED_RANGE, uv_color)

            # Determine Temperature color
            if temperature is None:
                temp_color = COLOR_BLACK
            elif 0 <= temperature <= 19:
                temp_color = COLOR_GREEN
            elif 20 <= temperature <= 29:
                temp_color = COLOR_YELLOW
            elif temperature >= 30:
                temp_color = COLOR_RED
            else:
                temp_color = COLOR_BLUE
            neopixel_manager.update_breathing(TEMP_LED_RANGE, temp_color)

            # Send data to ThingSpeak at defined intervals
            current_time = time.time()
            if current_time - last_thingspeak_update >= THINGSPEAK_DELAY:
                send_thingspeak(sensor_data)
                last_thingspeak_update = current_time
                data_counter += 1
                print(f"Success: Data transmission count is now {data_counter}")

            # Short, non-blocking sleep to prevent high CPU usage
            time.sleep(0.1)

    except KeyboardInterrupt:
        # Graceful shutdown on user interrupt
        print("Program terminated by user.")
        neopixel_manager.clear()
    except Exception as e:
        # Log unexpected errors elegantly
        print(f"An error occurred: {e}")
        neopixel_manager.clear()

if __name__ == "__main__":
    main()

help, i use battery instead of connect through usb but it doesn't work