#######################################################################################################
#######################################################################################################
# Opengreen energy weather station board, with ESP8266 D1 Mini
# https://www.opengreenenergy.com/solar-powered-wifi-weather-station-v2-0/
# zorruno 2025-09-12 V6 fixes: AP SSID sub; ADC calibrate indent; NaN-safe temp avg; empty-string publishes; improved 10min gust (3s avg -> 10m max); comment tidy-ups
# zorruno 2024-06-14 V5 added wind heading, and sorted breaking a change for OTA
# zorruno 2024-06-03 V4 tweaks to battery and wind
# zorruno 2024-05-25 V3 Shuffled some sensors, rain count added
# zorruno 2024-05-25 V2 A bunch more sensors added (wind direction not yet sorted)
# zorruno 2024-05-24 V1.1 Tested ok and added some V calibration for mine
# zorruno 2024-05-23 V1.0 Tidied up sensor info and added BMP280
# zorruno 2024-05-04 V0.0 Untested
#######################################################################################################
#######################################################################################################

#######################################################################################################
# SENSORS INCLUDED
#
# TESTED WORKING
# status_led:
# uptime:
# wifi_signal:
# battery voltage (adc A0)
#
# ads1115: 4 Channel A->D (0x48 i2c)
# dht: DHT22 Temp/Humidity, Remote (GPIO13)
# bmp280: Temp/pressure on the board (0x76 i2c)
# pulse_meter: Wind Speed (GPIO14)
# pulse_meter: Rainfall (GPIO12)
# ads1115 A0_GND: Wind Direction
#
# text_sensor: Beaufort Wind Scale Labelling
# text_sensor: Temp (av) Average of all temps
# text_sensor: Wind Direction Heading
#
# Battery Calcs:
# template: Last 30mins battery MAX
# template: battery now - max (discharge/charge)
# template binary: discharging, charging, stable
#
# TO TEST (HAVE SENSOR or doesn't need one)
# tsl2561: Ambient Light sensor (0x39 i2c)
# ads1115 A1_GND: UV Index
# ltr390: Ambient Light and UV sensor (0x53 i2c)
# pmsx003: PM Particulate Filter (UART)
# AS3935: lightning sensor (i2c, but needs design)
# ds1307: RTC (i2c)
#
# NOT USED (but code commented)
# bme280_i2c: Temp/humid/press (not used)
#
# POSSIBLY ADD
# Analogue for Solar V?
# ground movement/earthquake?
# water sensor (rain now)
# soil temp
# soil moisture
#
#######################################################################################################

#######################################################################################################
# WEMOS D1 Mini GPIO, and Weather station Use
#######################################################################################################
# PIN HEADERS on Weatherstation board:
# D1, GPIO5  Used as SCL (I2C)
# D2, GPIO4  Used as SDA (I2C)
# D3, GPIO0  connected to FLASH button, boot fails if pulled LOW
# D4, GPIO2  connected to on-board LED, boot fails if pulled LOW
# D5, GPIO14 Used as Wind Speed Count
# D6, GPIO12 Used as Rainfall count
# D7, GPIO13 Used for DHT
# D8, GPIO15 (Boot fails if pulled HIGH)
#
# NO HEADERS on Weatherstation board:
# D0, GPIO16 (used to wake up from deep sleep)
# RX, GPIO3  (HIGH at boot)
# TX, GPIO1  (HIGH at boot, boot fails if pulled LOW)
# A0, ADC0   Used here to measure battery V 
#
#######################################################################################################

#######################################################################################################
# Pulse Meter Issues 202405
# https://github.com/esphome/issues/issues/4807
# https://github.com/esphome/issues/issues/3143
#######################################################################################################
external_components:
  - source: github://TrentHouliston/esphome@loop_pulse_isr
    components: [ pulse_meter ]

#######################################################################################################
# Variable Substitutions
# Give this a useful name & description here
# and change values accordingly
#######################################################################################################
substitutions:
  devicename: "esp-weatherstation"
  friendly_name: "Weather Station"
  description_comment: "Opengreen energy board, with BME280 Temp/Hum/Pres Sensor on an ESP8266 D1 Mini"

  #if NOT using a secrets file, just replace these with the passwords etc in speech marks
  api_key: !secret esp-weatherstation_api_key #unfortunately you can't use substitutions in secrets names
  ota_pass: !secret esp-weatherstation_ota_pass #unfortunately you can't use substitutions in secrets names
  wifi_ssid: !secret wifi_ssid
  wifi_pass: !secret wifi_password
  mqtt_server: !secret mqtt_server
  mqtt_username: !secret mqtt_username
  mqtt_password: !secret mqtt_password

  mqtt_topic: "esphome" #main topic for the mqtt server, call it what you like
  update_time: 30s #update time for for various temp sensors etc

#######################################################################################################
# ESPHome
# https://esphome.io/components/esphome.html
#######################################################################################################
esphome:
  name: ${devicename}
  comment: ${description_comment} #appears on the esphome page in HA
  #on_boot:    #Initial Setting stuff
  #  priority: -200
  #  then:

#######################################################################################################
# ESP Platform and Framework
# https://esphome.io/components/esp32.html OR
# https://esphome.io/components/esp8266.html
#######################################################################################################
#esp32:
#  board: 
#  framework:
#    type: arduino
#    #type: esp-idf  #Suggested Use ESP-IDF Framework for ESP32, or unplugging the UART Cable Might Cause ESP32 Hang. 
#    version: recommended #recommended, latest or dev

esp8266:
  board: d1_mini

#######################################################################################################
# i2c bus
# https://esphome.io/components/i2c.html
# Put the relevant pins here, 4/5 are default for ESP8266
# and 21/22 for esp32 (although esp32 can have two buses)
#######################################################################################################
i2c:
  sda: GPIO4 
  scl: GPIO5
  scan: True  #scan the bus on startup.  Useful if looking for the address of something
  #frequency: 100kHz #10, 50, 100, 200, 800 are possible settings, 100kHz was reliable for me for ESP32, default is 50kHz

#############################################    
# ESPHome Logging Enable
# https://esphome.io/components/logger.html
############################################# 
logger:
  level: INFO  #INFO Level suggested, or DEBUG for testing
  #baud_rate: 0 #set to 0 for no logging via UART, needed if you are using it for other serial things (eg PZEM)
  #esp8266_store_log_strings_in_flash: false
  #tx_buffer_size: 64

#############################################    
# Enable the Home Assistant API
# https://esphome.io/components/api.html
#############################################  
api:
  encryption:
    key: ${api_key}

#############################################    
# Enable Over the Air Update Capability
# https://esphome.io/components/ota.html?highlight=ota
#############################################  
ota:
  - platform: esphome
    password: ${ota_pass}

#############################################    
# Safe Mode
# Safe mode will detect boot loops
# https://esphome.io/components/safe_mode
#############################################  
safe_mode:

#############################################    
# Wifi Settings 
# https://esphome.io/components/wifi.html
#
# Power Save mode (can reduce wifi reliability)
# NONE (least power saving, Default for ESP8266)
# LIGHT (Default for ESP32)
# HIGH (most power saving)
#############################################  
wifi:
  ssid: ${wifi_ssid}
  password: ${wifi_pass}
  #power_save_mode: LIGHT #https://esphome.io/components/wifi.html#wifi-power-save-mode
  #manual_ip: #optional static IP address
  #  static_ip: 192.168.x.x
  #  gateway: 192.168.X.x
  #  subnet: 255.255.255.0
  ap:   #Details for fallback hotspot in case wifi connection fails https://esphome.io/components/wifi.html#access-point-mode
    ssid: "${devicename} fallback AP"
    password: !secret fallback_ap_password
    ap_timeout: 30min #default is 1min

#############################################    
# Web Portal for display and monitoring
# Turning this off if you don't really need it
# is probably a good idea to save resources.
# Also, don't use it with other big ESP32 components
# such as Bluetooth proxy (it will run out of flash
# and not compile, or it will crash occasionally)
# https://esphome.io/components/web_server.html
#############################################   
#web_server:
#  port: 80
#  version: 1 #V1 occasionally works better, V2 The nicer page
#  username: !secret web_server_username #probably a good idea to secure it
#  password: !secret web_server_password

#######################################################################################################
# MQTT Monitoring
# https://esphome.io/components/mqtt.html?highlight=mqtt
# MUST also have api enabled if you enable MQTT
#######################################################################################################
mqtt:
  broker: ${mqtt_server}
  topic_prefix: ${mqtt_topic}/${devicename}
  username: ${mqtt_username}
  password: ${mqtt_password}

  #Method to prevent deep sleep using MQTT command
  #on_message:
  #  - topic: ${mqtt_topic}/${devicename}/deepsleep
  #    payload: 'ON'
  #    then:
  #      - deep_sleep.prevent: deep_sleep_1
  #  - topic: ${mqtt_topic}/${devicename}/deepsleep
  #    payload: 'ON'
  #    then:
  #      - deep_sleep.enter: deep_sleep_1
  
########################################    
# Deep Sleep
# https://esphome.io/components/deep_sleep.html
########################################   
#deep_sleep:
#  run_duration: 20s
#  sleep_duration: 10min
#  id: deep_sleep_1

#######################################################################################################
# General espHome status LED
# Not needed, but can be useful to see issues
# https://esphome.io/components/status_led.html
# If the LED appears inverted (on when it should be off), set 'inverted: true'.
#######################################################################################################
status_led:
  pin:
    number: GPIO2 #Wemos ESP32 and ESP8266 Onboard LEDs use GPIO2
    inverted: false
    #ignore_strapping_warning: True #https://esphome.io/guides/faq.html#why-am-i-getting-a-warning-about-strapping-pins

##########################################################################################
# 4 Input A->D sensor
# Analog sensor for voltage reading 
# https://esphome.io/components/sensor/ads1115.html
##########################################################################################
ads1115:
  - address: 0x48 #On the ADS1115 pull address pin to VCC for 0x49. Default is 0x48
       
#######################################################################################################
# Text Sensors 
# https://esphome.io/components/text_sensor/index.html
#######################################################################################################
text_sensor:
  - platform: template
    name: ${friendly_name} Beaufort Wind Scale
    icon: "mdi:tailwind"
    id: wind_scale
    update_interval: never
  - platform: template
    name: ${friendly_name} Wind Direction Heading
    icon: "mdi:tailwind"
    id: wind_dir_heading
    update_interval: never

#######################################################################################################
#######################################################################################################
# General Sensors 
# https://esphome.io/components/sensor/index.html
#######################################################################################################
#######################################################################################################
sensor:

  ################################
  # UPTIME
  # Device uptime info 
  # https://esphome.io/components/sensor/uptime.html
  ################################
  - platform: uptime
    name: ${friendly_name} Uptime

  ################################
  # WIFI SIGNAL
  # Quality of Wifi in dBm
  # https://esphome.io/components/sensor/wifi_signal.html
  ################################
  - platform: wifi_signal
    name: ${friendly_name} WiFi Signal
    update_interval: ${update_time}
    #retain: true #retain useful if sleeping
  
  ################################
  # BMP280 TEMPERATURE AND PRESSURE
  # The BMP does not have humidity, use the BME for that
  # https://esphome.io/components/sensor/bmp280.html
  ################################
  - platform: bmp280_i2c
    address: 0x76
    iir_filter: 16x
    update_interval: ${update_time}
    temperature:
      name: ${friendly_name} BMP280 Temperature
      id: bmp280_temp
      unit_of_measurement: "°C"
      oversampling: 16x
      #retain: true #MQTT retain useful if sleeping
    pressure:
      name: ${friendly_name} BMP280 Pressure
      unit_of_measurement: "hPa"
      oversampling: 16x
      #retain: true #MQTT retain useful if sleeping

  ################################
  # DHT TEMPERATURE, PRESSURE & HUMIDITY
  # https://esphome.io/components/sensor/dht.html
  # These DHTs are not very accurate...
  ################################
  - platform: dht
    pin: GPIO13 #D7 on the D1 Mini ESP8266
    temperature:
      name: ${friendly_name} DHT22 Temperature
      id: dht_temp
      unit_of_measurement: "°C"
      #retain: true #retain useful if sleeping
    humidity:
      name: ${friendly_name} DHT22 Humidity
      unit_of_measurement: "%"
      #retain: true #retain useful if sleeping
  
  ################################
  # TEMP SENSORS AVERAGE
  # Averaging for the various temp sensors on board
  ################################
  - platform: template
    name: ${friendly_name} Temp (av)
    icon: "mdi:thermometer"
    unit_of_measurement: "°C"
    lambda: |-
      const float a = id(bmp280_temp).state;
      const float b = id(dht_temp).state;
      const bool va = !isnan(a);
      const bool vb = !isnan(b);
      if (va && vb) return (a + b) / 2.0f;
      if (va) return a;
      if (vb) return b;
      return NAN;

  ################################
  # BATTERY VOLTAGE
  # Analog sensor for voltage reading 
  # (A0 on esp8266)
  # https://esphome.io/components/sensor/adc.html
  ################################
  - platform: adc
    name: ${friendly_name} Battery Voltage
    id: battery_voltage
    pin: A0
    unit_of_measurement: "V"
    accuracy_decimals: 6
    update_interval: 30s  
    #retain: true #retain useful if sleeping
    filters:
      - heartbeat: 30s
      - multiply: 5.223 # tested with multimeter
      - sliding_window_moving_average:
          window_size: 2 #2x 30s = 60s average period
          send_every: 1
          send_first_at: 1
      - calibrate_linear:
          - 3.321 -> 3.125
          - 3.89  -> 3.90
          - 4.09  -> 4.14

  ################################
  # A Calculation of recent Av V
  # So we can see if battery is charging/discharging
  ################################
  - platform: template
    name: ${friendly_name} Av Battery Voltage over recent period
    id: battery_voltage_recentav
    unit_of_measurement: "V"
    accuracy_decimals: 6
    update_interval: 30s  #updates every 30s
    lambda: |-
      return id(battery_voltage).state;  //grabs values from the battery_voltage 
    filters:
      - sliding_window_moving_average:
          window_size: 60 #60x 30s = 30 min average period
          send_every: 1
          send_first_at: 1

  ##########################################################################################
  # A Calculation of 10 min Max V
  # So we can see if battery is charging/discharging
  ##########################################################################################  
  - platform: template
    name: ${friendly_name} Max Battery Voltage over recent period
    id: battery_voltage_recentmax
    unit_of_measurement: "V"
    accuracy_decimals: 6
    update_interval: 30s  #updates every 30s
    lambda: |-
      return id(battery_voltage).state;  //grabs values from the battery_voltage 
    filters:
      - heartbeat: 30s
      - max:
          window_size: 20   # 20 x 30s = 10 min max value (was 60 = 30 min)
          send_every: 1
          send_first_at: 1

  - platform: template
    name: ${friendly_name} Battery Voltage loss over 10 mins
    id: battery_voltage_recentloss
    unit_of_measurement: "V"
    accuracy_decimals: 5
    update_interval: 30s  #updates every 30s
    lambda: |-
      return id(battery_voltage_recentmax).state - id(battery_voltage).state;  //recent max voltage - now

  - platform: duty_time
    id: battery_discharge_time
    name: ${friendly_name} Time battery is discharging
    unit_of_measurement: "Mins"
    retain: true  #retain this as when device dies, we still want the value
    # Support logical sources (optional): 'binary_sensor'
    accuracy_decimals: 0
    filters:
      - multiply: 0.01666666
      - round: 0
    lambda: |-
      return id(battery_voltage).state <= id(battery_voltage_recentav).state;

##########################################################################################
  # RAIN SENSOR
  # Pulse Meter for measuring rainfall
  # https://esphome.io/components/sensor/pulse_meter.html
##########################################################################################
  # CALIBRATION HINTS
  # https://forum.mysensors.org/topic/9594/misol-rain-gauge-tipping-bucket-rain-amount/2
##########################################################################################
  - platform: pulse_meter
    id: rainfall_meter
    pin: 
      number: GPIO12 #D6 on D1 Mini
      mode:
        input: true
        pullup: true
    internal_filter_mode: EDGE
    internal_filter: 10ms
    name: ${friendly_name} Rainfall
    #icon: mdi:
    unit_of_measurement: "mm"
    accuracy_decimals: 1
    timeout: 5s
    filters:
      - heartbeat: 2s
      #- timeout: 
      #    timeout: "5s"  #after 5 seconds, if no pulses received....
      #    value: 0       #make the value = 0
      - multiply: 0.2794 #mm of rain from tip bucket, 0.28 mm per pulse, or 3.57 pulse per mm (NEEDS CALIBRATION)
      - sliding_window_moving_average: # Moving average to prevent too many data points
          window_size: 4
          send_every: 1
      #- clamp:
      #    min_value: 0
      #    max_value: 250 #if anything over that, we have debounce issues or are going to die
      #    ignore_out_of_range: true

##########################################################################################
  # Pulse Meter for measuring wind speed
  # Analog sensor for voltage reading 
  # https://esphome.io/components/sensor/pulse_meter.html
  #
  # This Anemometer https://www.sparkfun.com/datasheets/Sensors/Weather/Weather%20Sensor%20Assembly..pdf
  # The cup-type anemometer measures wind speed by closing a contact
  # as a magnet moves past a reed switch. A wind speed of 1.492mph (2.4km/h) (0.667m/s)
  # causes the switch to close once per second. The reed switch closes twice per revolution.
  # 1rps = 60rpm = 2.4kmh
  # 1pulse/min = 2.4kmh/60 = 0.04kmh
  # Pulse meter component measures the time between rising edges on a pin, 
  # for each pulse it outputs the frequency in pulses/min.
##########################################################################################
  # USEFUL LINKS
  # https://community.home-assistant.io/t/measuring-wind-speed/395693/20
  # https://community.home-assistant.io/t/measuring-wind-speed/395693/21
  #############################################
  - platform: pulse_meter # https://community.home-assistant.io/t/measuring-wind-speed/395693
    id: wind_meter
    pin: 
      number: GPIO14 #D5 on D1 Mini
      mode:
        input: true
        pullup: true
    #internal: true # If true, don't send to HA/MQTT etc
    internal_filter_mode: EDGE
    internal_filter: 10ms
    name: ${friendly_name} Wind speed
    icon: "mdi:weather-windy"
    unit_of_measurement: "km/h"
    accuracy_decimals: 1
    timeout: 5s
    filters:
      - heartbeat: 2s
      #- timeout: 
      #    timeout: "5s"  #after 5 seconds, if no pulses received....
      #    value: 0       #make the value = 0
      - multiply: 0.04 #kmh
      - sliding_window_moving_average: # Moving average to prevent too many data points
          window_size: 4
          send_every: 1
      - clamp:
          min_value: 0
          max_value: 250 #if anything over that, we have debounce issues or are going to die
          ignore_out_of_range: true

##########################################################################################
# WIND GUSTS - WMO style 10 minute gust
# Per WMO guidance, "gust" ≈ maximum of 3-second mean wind over the last 10 minutes.
# Implementation:
#   1) Compute a 3 s sliding average (updated every 1 s) from wind_meter.
#   2) Take the rolling maximum of that 3 s average over the last 10 minutes.
##########################################################################################
  - platform: template
    id: wind_speed_3s_avg
    name: ${friendly_name} Wind speed (3s avg)
    unit_of_measurement: "km/h"
    icon: "mdi:weather-windy"
    update_interval: 1s
    lambda: |-
      return id(wind_meter).state;  // sample latest wind speed
    filters:
      - filter_out: nan
      - sliding_window_moving_average:
          window_size: 3     # 3 samples x 1s = 3 s average
          send_every: 1
          send_first_at: 1

  - platform: template
    name: ${friendly_name} Windspeed Gust (Max last 10m)
    unit_of_measurement: "km/h"
    icon: "mdi:weather-windy"
    update_interval: 1s
    lambda: |-
      return id(wind_speed_3s_avg).state;  # use the 3 s average stream
    filters:
      - filter_out: nan
      - max:
          window_size: 600   # 600 x 1s = 10 minutes rolling window
          send_every: 1
          send_first_at: 1   # after first 3 s average becomes meaningful

##########################################################################################
  # BEAUFORT SCALE
  # Template for Beaufort for measuring wind speed
  # The output updates the Text Sensor ID wind_scale
  # https://windy.app/blog/wind-speed-beaufort-scale.html
##########################################################################################
  - platform: template
    #name: ${friendly_name} Windspeed Scale
    internal: true # no need to show this externally as it is just for calculating Beaufort 
    #icon: "mdi:weather-windy"
    id: wind_meter_scale
    lambda: |-
      return id(wind_meter).state;
    #unit_of_measurement: "m/s"
    update_interval: 5s
    filters:
      - throttle_average: 5s
      - multiply: 0.278 #to get m/s from km/h
    on_value:
      lambda: |-
        if (id(wind_meter_scale).state < 0.1) {
          id(wind_scale).publish_state("Calm");
        } else if (id(wind_meter_scale).state > 0 && id(wind_meter_scale).state < 2) {
          id(wind_scale).publish_state("Light Air");
        } else if (id(wind_meter_scale).state >= 2 && id(wind_meter_scale).state < 3) {
          id(wind_scale).publish_state("Light Breeze");
        } else if (id(wind_meter_scale).state >= 3 && id(wind_meter_scale).state < 5) {
          id(wind_scale).publish_state("Gentle Breeze");
        } else if (id(wind_meter_scale).state >= 5 && id(wind_meter_scale).state < 8) {
          id(wind_scale).publish_state("Moderate Breeze");
        } else if (id(wind_meter_scale).state >= 8 && id(wind_meter_scale).state < 11) {
          id(wind_scale).publish_state("Fresh Breeze");
        } else if (id(wind_meter_scale).state >= 11 && id(wind_meter_scale).state < 14) {
          id(wind_scale).publish_state("Strong Breeze");
        } else if (id(wind_meter_scale).state >= 14 && id(wind_meter_scale).state < 17) {
          id(wind_scale).publish_state("Near Gale");
        } else if (id(wind_meter_scale).state >= 17 && id(wind_meter_scale).state < 21) {
          id(wind_scale).publish_state("Gale");
        } else if (id(wind_meter_scale).state >= 21 && id(wind_meter_scale).state < 24) {
          id(wind_scale).publish_state("Severe Gale");
        } else if (id(wind_meter_scale).state >= 24 && id(wind_meter_scale).state < 28) {
          id(wind_scale).publish_state("Storm");
        } else if (id(wind_meter_scale).state >= 28 && id(wind_meter_scale).state < 33) {
          id(wind_scale).publish_state("Violent Storm");
        } else if (id(wind_meter_scale).state >= 33) {
          id(wind_scale).publish_state("Hurricane Force");
        } else {
          id(wind_scale).publish_state("");
        }

  #############################################
  # WIND DIRECTION SENSOR
  # looks like this one
  # https://www.sparkfun.com/datasheets/Sensors/Weather/Weather%20Sensor%20Assembly..pdf
  # uses black and green cables for R value
  #############################################
  # Wind Direction, Analogue on ADS115
  # Analog sensor for voltage reading 
  # https://esphome.io/components/sensor/ads1115.html
##########################################################################################
  # Using a analog wind direction sensor that has an output 
  # of 0 - 5 V for 360° representing the position of the arrow
  # from the north pole. This is 5V divided by 8 resistor 
  # values giving the output voltage value for each azimuth. 
  # We could also report a compass direction.
  ##########################################################################################
  # Useful Links
  # https://community.home-assistant.io/t/davis-wind-direction-equipment-and-esp-home/508764
  ##########################################################################################
  - platform: ads1115
    multiplexer: 'A0_GND'
    gain: 4.096
    name: ${friendly_name} Wind Direction Voltage
    id: wind_direction_voltage
    update_interval: 2s
    on_value:
      lambda: |-
        const float v = x;  // use current reading from on_value
        if (v >= 0.57816 && v <= 0.58984) {
          id(wind_dir_heading).publish_state("E");
        } else if (v >= 1.05039 && v <= 1.07161) {
          id(wind_dir_heading).publish_state("SE");
        } else if (v >= 1.49094 && v <= 1.52106) {
          id(wind_dir_heading).publish_state("S");
        } else if (v >= 2.08692 && v <= 2.12908) {
          id(wind_dir_heading).publish_state("NE");
        } else if (v >= 2.53440 && v <= 2.58560) {
          id(wind_dir_heading).publish_state("SW");
        } else if (v >= 2.87100 && v <= 2.92900) {
          id(wind_dir_heading).publish_state("N");
        } else if (v >= 3.05712 && v <= 3.11888) {
          id(wind_dir_heading).publish_state("NW");
        } else if (v >= 3.15414 && v <= 3.21786) {
          id(wind_dir_heading).publish_state("W");
        } else {
          id(wind_dir_heading).publish_state("");
        }

  ##########################################################################################
  # BME280 temp/humidity/pressure (Note, BME and BMP Are different...)
  # https://esphome.io/cookbook/bme280_environment.html
  ##########################################################################################
  #- platform: bme280_i2c
  #  address: 0x76
  #  update_interval: ${update_time}
  #  temperature:
  #    name: ${friendly_name} BME280 Temp
  #    accuracy_decimals: 1     
  #    oversampling: 2x
  #    unit_of_measurement: "°C"
  #    #retain: true #MQTT retain useful if sleeping
  #  pressure:
  #    name: ${friendly_name} BME280 Pressure
  #    oversampling: 2x
  #    unit_of_measurement: "hPa"
  #    #retain: true #MQTT retain useful if sleeping
  #  humidity:
  #    name: ${friendly_name} BME280 Humidity
  #    accuracy_decimals: 1     
  #    oversampling: 2x
  #    unit_of_measurement: "%"
  #    #retain: true #MQTT retain useful if sleeping


  ##########################################################################################
  # TSL2561 ambient light sensor
  # https://esphome.io/components/sensor/tsl2561.html
  ##########################################################################################
  #- platform: tsl2561
  #  name: ${friendly_name} Ambient Light
  #  address: 0x39
  #  update_interval: 60s

  ##########################################################################################
  # LTR390 Ambient Light and UV Sensor
  # https://esphome.io/components/sensor/ltr390.html
  ##########################################################################################
  #- platform: ltr390
  #  address: 0x35
  #  uv_index:
  #    name: ${friendly_name} 390 UV Index
  #  uv:
  #    name: ${friendly_name} 390 UV Sensor Counts
  #  light:
  #    name: ${friendly_name} 390 Light
  #  ambient_light:
  #    name: ${friendly_name} 390 UV Sensor Counts
  #  update_interval: 60s

  ##########################################################################################
  # PMSX003 Particulate sensor
  # NEEDS UART SET FIRST
  # https://esphome.io/components/sensor/pmsx003.html
  ##########################################################################################
  #- platform: pmsx003
  #  type: PMSX003
  #  pm_1_0:
  #    name: ${friendly_name} Particulate Matter <1.0µm
  #  pm_2_5:
  #    name: ${friendly_name} <2.5µm
  #  pm_10_0:
  #    name: ${friendly_name} <10.0µm Concentration





    
#    filters:   
#      lambda: |-
#         if( (x * (360.0/5.0)) >= 10.0 &&  (x * (360.0/5.0)) <= 70.0  ) { return x = 45 };
#         else if( (x * (360.0/5.0)) > 70.0 &&  (x * (360.0/5.0)) <= 120.0  ) { return x = 90 };
#         else if( (x * (360.0/5.0)) > 120.0 &&  (x * (360.0/5.0)) <= 170.0  ) { return x = 135 };
#         else if( (x * (360.0/5.0)) > 170.0 &&  (x * (360.0/5.0)) <= 215.0  ) { return x = 180 };
#         else if( (x * (360.0/5.0)) > 215.0 &&  (x * (360.0/5.0)) <= 262.0  ) { return x = 225 };
#         else if( (x * (360.0/5.0)) > 262.0 &&  (x * (360.0/5.0)) <= 330.0  ) { return x = 270 };
#         else if( (x * (360.0/5.0)) > 330.0 &&  (x * (360.0/5.0)) <= 355.0  ) { return x = 315 };
#         else if(((x * (360.0/5.0)) > 355.0 &&  (x * (360.0/5.0)) <= 360.0) || ((x * (360.0/5.0)) >= 0.0 &&  (x * (360.0/5.0)) <= 10.0)) { return x = 0 };

#  - platform: template
#    name: ${friendly_name} Winddirection Degrees
#    id: wind_direction_voltage
#    lambda: return id(wind_direction_voltage).state;
#    unit_of_measurement: "degrees"
#    update_interval: 5s
#    filters:
#      - throttle_average: 5s
#    on_value:
#      lambda: |-
#        if (id(wind_direction_voltage).state * (360.0/5.0) >= 10.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 70.0) {
#          id(wind_dir_heading).publish_state("45");
#        } else if (id(wind_direction_voltage).state * (360.0/5.0) = 70.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 120.0) {
#          id(wind_dir_heading).publish_state("90");
#        } else if (id(wind_direction_voltage).state * (360.0/5.0) = 120.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 170.0) {
#          id(wind_dir_heading).publish_state("135");
#        } else if (id(wind_direction_voltage).state * (360.0/5.0) = 170.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 215.0) {
#          id(wind_dir_heading).publish_state("180");
#        } else if (id(wind_direction_voltage).state * (360.0/5.0) = 215.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 262.0) {
#          id(wind_dir_heading).publish_state("225");
#        } else if (id(wind_direction_voltage).state * (360.0/5.0) = 262.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 330.0) {
#          id(wind_dir_heading).publish_state("270");
#        } else if (id(wind_direction_voltage).state * (360.0/5.0) = 330.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 355.0) {
#          id(wind_dir_heading).publish_state("315");
#        } else if (id(wind_direction_voltage).state * (360.0/5.0) = 355.0 && id(wind_direction_voltage).state * (360.0/5.0) <= 360.0) {
#          id(wind_dir_heading).publish_state("0");
#        } else {
#          id(wind_dir_heading).publish_state("0");
#        }

#  - platform: ads1115
#    multiplexer: "A1_GND"
#    gain: 6.144
#    name: ${friendly_name} UV Index
#    update_interval: 10s
#    unit_of_measurement: "mW/cm2"
#    accuracy_decimals: 1




#    on_value:
#      lambda: |-
#        if(x > id(g_WindGust_10min)) {
#        id(g_WindGust_10min) = x;
#        id(id_wind_gust_10min).publish_state(id(g_WindGust_10min));
#        };
#          //id(g_wind_run) += (x>0) ? 0.000666982 : 0;
#          if(x > id(g_WindGust_10s)) {
#            id(g_WindGust_10s) = x;
#            id(id_wind_gust_10s).publish_state(id(g_WindGust_10s));
#          };
#         if(x > id(g_WindGust_60s)) {
#            id(g_WindGust_60s) = x;
#            id(id_wind_gust_60s).publish_state(id(g_WindGust_60s));
#          };


#          if(id(g_WindSpeedMin_Reset)){
#            id(g_WindSpeedMin_10s) = x;
#            id(g_WindSpeedMin_Reset) = false;
#          } else {
#            if(x < id(g_WindSpeedMin_10s)) {id(g_WindSpeedMin_10s) = x;};
#          }
#    total:
#      name: "Roof Wind Run daily"
#      id: id_wind_run_daily
#      internal: false
#      unit_of_measurement: "km"
#      accuracy_decimals: 3
#      filters:
#        - multiply: 0.000666982
#        - throttle: 30s





# https://www.reddit.com/r/Esphome/comments/kndues/take_max_value_over_time/
#  - platform: template
#    name: ${friendly_name} Windspeed last 10min max
#    #id: ${node_name}_pm_1_0_rolling_30_minute_average
#    unit_of_measurement: "km/h"
#    update_interval: 60s
#    lambda: |-
#      const size_t window_size_ = 10;
#      const size_t send_every_ = 10;
#      static size_t send_at_ = 0;

#      static std::deque<float> queue_;
#      if (!isnan(x)) {
#        if (queue_.size() >= window_size_) {
#          queue_.pop_front();
#        }
#        queue_.push_back(x);
#      }

#      if (++send_at_ >= send_every_) {
#        send_at_ = 0;
#        if (!queue_.empty()) {
#          std::deque<float>::iterator it = std::max_element(queue_.begin(), queue_.end());
#          return *it;
#        }
#        return 0.0f;
#      }
#      return {};


#  - platform: pulse_meter
#    pin: 
 #     number: GPIO13  #This is Pin D7 on the D1 Mini ESP8266
 #     mode:
#        input: true
#        pullup: true
#    name: ${friendly_name} Wind speed
#    id: wind_meter
#    unit_of_measurement: m/s
#    accuracy_decimals: 1
#    timeout: 5s
#    internal_filter_mode: EDGE
#    internal_filter: 10ms

    # A wind speed of 2.4km/h (1.492mph) causes the switch to close once per second.
    # that is 0.667m/s.
    # Switch closes twice per revolution
      
    # rotations_per_sec = pulses / 2 / 60
    # circ_m = 0.09 * 2 * 3.14 = 0.5652
    # mps = 1.18 * circ_m * rotations_per_sec  
    # mps = 1.18 * (0.5652 / 2 / 60) = 0.0055578
#
#    filters:
#      # - multiply: 0.0055578 #use for m/s
#      # - multiply: 2.237 #m/s to mph
#      # - multiply: 0.0124327986 #m/s * mph conversion
#      # - multiply: ? #m/s to kph
#      - multiply: 0.0055578
#      - sliding_window_moving_average: # Helps prevent too many datapoints
#          window_size: 10
#          send_every: 10



#switch:
#  - platform: template
#    optimistic: true
#    name: switch
#    #internal: True
#    id: g_WindSpeedMin_Reset

#interval:
#  - interval: 10s
#    then:
#    - lambda: |-
#        id(g_WindSpeedMin_Reset).state == true;
#          //id(id_wind_gust_10s).publish_state(id(g_WindGust_10s));
#          id(id_wind_speed_min_10s).publish_state(id(g_WindSpeedMin_10s));
#          //id(id_wind_gust_60s).publish_state(id(g_WindGust_60s));
#          //id(id_wind_gust_10min).publish_state(id(g_WindGust_10min));
#          id(g_WindGust_10s) = 0;
#          // id(g_WindSpeedMin_10s) = 9999.9;
#        id(g_WindSpeedMin_Reset) = true;
#  - interval: 60s
#    then:
#      - lambda: |-
#          id(g_WindGust_60s) = 0;
#          //maintain rain
#          //if (++id(g_minute) > 59) id(g_minute) = 0;
#          //id(gt_rain_hour)[id(g_minute)] = 0;
#  - interval: 10min
#    then:
#    - lambda: |-
#        id(g_WindGust_10min) = 0;


#globals:
#  global variables for wind/rain sensors
#  - id: g_WindGust_10min
#    type: float
#    restore_value: no
#    initial_value: '0.0'
#  - id: g_wind_run_count
#    type: int
#    restore_value: no
#    initial_value: '0'
#  - id: g_rain_count
#    type: int
#    restore_value: no
#    initial_value: '0'
#  - id: gt_rain_hour
#    type: byte[60]
#    restore_value: no
#  - id: g_minute
#    type: byte
#    restore_value: no
#    initial_value: '0'
#  - id: g_WindGust_10s
#    type: float
#    restore_value: no
#    initial_value: '0.0'
#  - id: g_WindSpeedMin_10s
#    type: float
#    restore_value: no
#    initial_value: '9999.9'
#  - id: g_WindGust_60s
#    type: float
#    restore_value: no
#    initial_value: '0.0'
