AguacaTEC Team
Published © Apache-2.0

Habbit Desk PRO (M5Stack TAB5)

Home Assistant control in your desk!

IntermediateFull instructions provided17
Habbit Desk PRO (M5Stack TAB5)

Things used in this project

Hardware components

M5Stack Tab5 IoT Development Kit (ESP32-P4)
M5Stack Tab5 IoT Development Kit (ESP32-P4)
×1

Software apps and online services

Home Assistant
Home Assistant

Story

Read more

Code

ESPHome Habbit Desk PRO

YAML
substitutions:

# Device customization
# Personalización del dispositivo

  name: habbit-desk-pro
  friendly_name: Habbit Desk PRO
  background_color: 0x000000
  text_color: 0xFFFFFF
  text_color_secondary: 0x767676

  iddle_time: 20s

  animation_active: https://aguacatec.es/wp-content/uploads/2026/04/anim_active-1.png
  animation_iddle: https://aguacatec.es/wp-content/uploads/2026/04/anim_iddle-1.png
  animation_alert: https://aguacatec.es/wp-content/uploads/2026/04/notification-1.png

  icon_ha: https://aguacatec.es/wp-content/uploads/2026/04/icon_ha.png

  image_3dprinter: https://aguacatec.es/wp-content/uploads/2026/04/3dprinter_enclosed.png
  image_vacuum: https://aguacatec.es/wp-content/uploads/2026/04/vacuum.png

# Entities
# Entidades

  weather_condition: weather.openweathermap
  room_temperature: sensor.sensor_temperatura_estudio_temperature
  room_humidity: sensor.sensor_temperatura_estudio_humidity
  room_co2: sensor.wifi_smart_switch_calidad_del_aire

  notifications: input_text.notificaciones_habbit_desk
  calendar: sensor.calendar
  media: media_player.laptopchuwi
  goal_current: sensor.aguacatec_suscriptores
  goal_target: 3000
  goal_text: "A por los 3000 subs"

  light_ceiling: light.ventilador_estudio
  cover1: cover.estor_estudio_1
  cover2: cover.estor_estudio_2
  climate: climate.salon
  dehumidifier: humidifier.deshumidificador_estudio
  fan_ceiling: fan.ventilador_estudio
  light_bar: switch.regleta_usb_escritorio_l2
  printer: switch.impresora

  printer_3d: switch.impresora_3d
  printer_3d_octoprint_status: switch.regleta_usb_herramientas_center
  printer_3d_octoprint_file: sensor.octoprint_current_file
  printer_3d_octoprint_temp_bed: sensor.octoprint_actual_bed_temp
  printer_3d_octoprint_temp_hotend: sensor.octoprint_actual_tool0_temp
  printer_3d_octoprint_operation: sensor.octoprint_current_state
  printer_3d_octoprint_progress: sensor.octoprint_job_percentage
  printer_3d_octoprint_finish: sensor.octoprint_estimated_finish_time
  printer_3d_pause_button: button.octoprint_pause_job
  printer_3d_resume_button: button.octoprint_resume_job
  printer_3d_stop_button: button.octoprint_stop_job

  vacuum: vacuum.roborock_qr_598
  vacuum_water: binary_sensor.roborock_qr_598_escasez_de_agua
  vacuum_error: sensor.roborock_qr_598_error_de_aspirador
  vacuum_current_room: sensor.roborock_qr_598_habitacion_actual
  vacuum_cleaning_progress: sensor.roborock_qr_598_progreso_de_la_limpieza
  vacuum_cleaning_time: sensor.roborock_qr_598_tiempo_de_limpieza
  vacuum_last_clean: sensor.roborock_qr_598_finalizacion_de_la_ultima_limpieza

# Other settings
# Otros ajustes

  allowed_characters: " []¿?¡!#%'()+,-—_./:µ³°ªº0123456789ABCDEFGHIJKLMNÑOPQRSTUVWYZabcdefghijklmnñopqrstuvwxyzáéíóúÁÉÍÓÚäëïöü"

################################################################################################################

esphome:
  name: ${name}
  friendly_name: ${friendly_name}
  on_boot:
    priority: -100
    then:

      ## Speaker
      ## Altavoz

      - switch.turn_on: speaker_enable
      - delay: 500ms
      - select.set:
          id: dac_output_select    
          option: LINE1
      - delay: 500ms
      - media_player.volume_set:
          id: speaker_player
          volume: 1

      ## Animation
      ## Animación

      - lambda: |-
          lv_obj_t* obj = id(animation_widget);
          int32_t base_y = lv_obj_get_y(obj);
          static lv_anim_t anim;
          lv_anim_init(&anim);
          lv_anim_set_var(&anim, obj);
          lv_anim_set_exec_cb(&anim, [](void* o, int32_t v) {
            lv_obj_set_y((lv_obj_t*)o, v);
          });
          lv_anim_set_values(&anim, base_y, base_y + 30);
          lv_anim_set_duration(&anim, 2200);
          lv_anim_set_playback_duration(&anim, 2200);
          lv_anim_set_repeat_count(&anim, LV_ANIM_REPEAT_INFINITE);
          lv_anim_set_path_cb(&anim, lv_anim_path_ease_in_out);
          lv_anim_start(&anim);

      - component.update: ina226_sensor
      - delay: 500ms
      - component.update: battery_percentage

esp32:
  board: esp32-p4-evboard
  flash_size: 16MB
  framework:
    type: esp-idf
    advanced:
      enable_idf_experimental_features: true

esp32_hosted:
  variant: esp32c6
  active_high: true
  clk_pin: GPIO12
  cmd_pin: GPIO13
  d0_pin: GPIO11
  d1_pin: GPIO10
  d2_pin: GPIO9
  d3_pin: GPIO8
  reset_pin: GPIO15
  slot: 1

logger:
  hardware_uart: USB_SERIAL_JTAG

psram:
  mode: hex
  speed: 200MHz

api:
  encryption:
    key: "Fdasdas+OaKt1XvQV9pU6dsaadsads"

ota:
  - platform: esphome
    password: "2r3d3908b12744trgwds"

wifi:
  ssid: !secret wifi_ssid
  password: !secret wifi_password
  ap:
    ssid: "M5Stack-Tab5 Fallback Hotspot"
    password: "ewfef3Mt2rd"

audio_dac:
  - platform: es8388
    id: es8388_dac

audio_adc:
  - platform: es7210
    id: es7210_adc
    bits_per_sample: 16bit
    sample_rate: 16000

# Sensors configuration
# Configuración de sensores

binary_sensor:
  - platform: gpio
    id: charging
    name: "Carga"
    icon: mdi:battery-charging
    pin:
      pi4ioe5v6408: pi4ioe2
      number: 6
      mode: INPUT_PULLDOWN
    filters:
      - delayed_on: 2s       
      - delayed_off: 2s     
    on_state:
      then:
      - lvgl.label.update:
          id: lbl_battery_percentage
          text:
            format: "%.0f%%"
            args:
              - id(battery_percentage).state
      - lvgl.label.update:
          id: battery_icon
          text: !lambda |-
            if (id(charging).state) return "\U0000f250";  
            float pct = x;
            if (pct >= 70.0) return "\U0000f24f";
            if (pct >= 30.0) return "\U0000f255";
            return "\U0000f257";
          text_color: !lambda |-
            float pct = id(battery_percentage).state;
            if (id(charging).state) return lv_color_hex(0xe6b854);
            if (pct >= 70.0) return lv_color_hex(0xc9e654);
            if (pct >= 30.0) return lv_color_hex(0xe65476);
            return lv_color_hex(0x767676);

  - platform: gpio
    id: headphone_detect
    name: "Headphone Detect"
    internal: true
    pin:
      pi4ioe5v6408: pi4ioe1
      number: 7

  # Devices
  # Dispositivos

  # VACUUM

  - platform: homeassistant
    id: device_vacuum_water
    entity_id: ${vacuum_water}
    internal: true
    filters:
      - delayed_on: 10min     
    on_press:
      then:
        - lvgl.label.update:
            id: lbl_device_vacuum_water
            text_color: 0xe65470
        - lvgl.image.update:
            id: icon_vacuum_water_state
            image_recolor: 0xe65470
    on_release:
      then:
        - lvgl.label.update:
            id: lbl_device_vacuum_water
            text_color: 0x767676
        - lvgl.image.update:
            id: icon_vacuum_water_state
            image_recolor: 0x767676

esp_ldo:
  - voltage: 2.5V
    channel: 3

font:

  # Interface
  # Interfaz

  - file: "gfonts://Figtree"
    id: font_clock
    size: 160
    glyphs: ${allowed_characters}
  - file: "gfonts://Material+Symbols+Outlined"
    id: font_info_icons
    size: 40
    glyphs: [
      "\U0000f24f", # 100%
      "\U0000f255", # 50%
      "\U0000f257", # 10%
      "\U0000f250", # Charging
      "\U0000e98e", # Speaker ON
      "\U0000e04f", # Speaker OFF
    ]
  - file: "gfonts://Material+Symbols+Outlined"
    id: font_info_icons_little
    size: 40
    glyphs: [
      "\U0000f654", # Alert
      "\U0000e63e", # Wifi ON
      "\U0000e648", # Wifi OFF
      "\U0000e1ff", # thermometer
      "\U0000f87e", # water-percent
      "\U0000e29c", # air
      "\U0000e834", # check
      "\U0000e62e", # assistant
    ]

  - file: "gfonts://Figtree"
    id: font_notification
    size: 70
    glyphs: ${allowed_characters}

  # Weather
  # Meteorología

  - file: "gfonts://Material+Symbols+Outlined"
    id: font_top_icons
    size: 70
    glyphs: [
      "\U0000e81a", # wb_sunny (sol)
      "\U0000e818", # cloud (nube)
      "\U0000f176", # umbrella (lluvia)
      "\U0000e810", # water_drop (gotas)
      "\U0000e80b", # ac_unit (nieve)
      "\U0000efd8", # air (viento)
      "\U0000ea0b", # thunderstorm (tormenta)
      "\U0000f159", # nights_stay (noche)
    ]

  - file: "gfonts://Kanit"
    id: font_info_text
    size: 35
    glyphs: ${allowed_characters}

  - file: "gfonts://Material+Symbols+Outlined"
    id: font_device_icons
    size: 120
    glyphs: [
      "\U0000f02a", # ceiling-light
      "\U0000f168", # fan
      "\U0000e286", # blinds-shade
      "\U0000ec1f", # blinds-shade-close
      "\U0000ec12", # roller-shade
      "\U0000ec11", # roller-shade-close
      "\U0000ed38", # 3d
      "\U0000ef55", # fire
      "\U0000efc5", # vacuum
      "\U0000f7ed", # led-strip
      "\U0000e97e", # dehumidifier
      "\U0000e1c4", # play
      "\U0000e1a2", # pause
      "\U0000eaaa", # base
      "\U0000eee1", # locate
      "\U0000f418", # power
      "\U0000ef71", # stop
      "\U0000ebfe", # spotlight
      " ",
    ]
  - file: "gfonts://Material+Symbols+Outlined"
    id: font_device_icons_little
    size: 60
    glyphs: [
      "\U0000e879", # exit
      "\U0000e548", # more
      "\U0000e834", # check
      " ",
    ]
  - file: "gfonts://Material+Symbols+Outlined"
    id: font_details_icons
    size: 60
    glyphs: [
      "\U0000e286", # blinds-shade
      "\U0000ec1f", # blinds-shade-close
      " ",
    ]
  - file: "gfonts://Kanit"
    id: font_value
    size: 60
    glyphs: ${allowed_characters}

  - file: "gfonts://Kanit"
    id: font_device_status
    size: 30
    glyphs: ${allowed_characters}
  - file: "gfonts://Kanit"
    id: font_device_name
    size: 35
    glyphs: ${allowed_characters}
  - file: "gfonts://Kanit"
    id: font_values
    size: 30
    glyphs: ${allowed_characters}

globals:

  ## Notifications
  ## Notificaciones

  - id: notification
    type: bool
    restore_value: no
    initial_value: 'false'
  - id: notification_last_changed
    type: time_t
    restore_value: yes
    initial_value: '0'
  - id: notification_last_text
    type: std::string
    restore_value: yes
    max_restore_data_length: 254
    initial_value: '""'

i2c:
  - id: bsp_bus
    sda: GPIO31
    scl: GPIO32
    frequency: 400kHz

i2s_audio:
  - id: mic_bus
    i2s_lrclk_pin: GPIO29
    i2s_bclk_pin: GPIO27
    i2s_mclk_pin: GPIO30

image:

  ## Customization
  ## Personalización

  - file: ${animation_active}
    id: img_animation_active
    resize: 400x400
    type: RGB565
    transparency: alpha_channel
  - file: ${animation_iddle}
    id: img_animation_iddle
    resize: 400x400
    type: RGB565
    transparency: alpha_channel
  - file: ${animation_alert}
    id: img_animation_alert
    resize: 400x400
    type: RGB565
    transparency: alpha_channel

  ## Icons
  ## Iconos

  - file: ${icon_ha}
    id: img_icon_ha
    resize: 100x100
    type: RGB565
    transparency: alpha_channel

  - file: mdi:calendar
    id: icon_calendar_mini
    resize: 60x60
    type: BINARY
    transparency: chroma_key
  - file: mdi:check-bold
    id: icon_check
    resize: 60x60
    type: BINARY
    transparency: chroma_key
  - file: mdi:alert-decagram-outline
    id: icon_notification
    resize: 400x400
    type: BINARY
    transparency: chroma_key

  - file: mdi:microphone
    id: icon_assist_active
    resize: 400x400
    type: BINARY
    transparency: chroma_key
  - file: mdi:timer-sand
    id: icon_assist_thinking
    resize: 400x400
    type: BINARY
    transparency: chroma_key
  - file: mdi:check-bold
    id: icon_assist_success
    resize: 400x400
    type: BINARY
    transparency: chroma_key

  - file: mdi:music
    id: icon_music_mini
    resize: 60x60
    type: BINARY
    transparency: chroma_key
  - file: mdi:play-pause
    id: icon_music_toggle
    resize: 60x60
    type: BINARY
    transparency: chroma_key
  - file: mdi:skip-previous
    id: icon_music_previous
    resize: 60x60
    type: BINARY
    transparency: chroma_key
  - file: mdi:skip-next
    id: icon_music_next
    resize: 60x60
    type: BINARY
    transparency: chroma_key

  - file: mdi:youtube
    id: icon_youtube_mini
    resize: 60x60
    type: BINARY
    transparency: chroma_key

  - file: mdi:robot-vacuum
    id: icon_vacuum
    resize: 110x110
    type: BINARY
    transparency: chroma_key
  - file: mdi:robot-vacuum
    id: icon_vacuum_mini
    resize: 60x60
    type: BINARY
    transparency: chroma_key
  - file: mdi:water
    id: icon_vacuum_water
    resize: 40x40
    type: BINARY
    transparency: chroma_key
  - file: mdi:robot-vacuum-alert
    id: icon_vacuum_error
    resize: 40x40
    type: BINARY
    transparency: chroma_key

  - file: mdi:printer-3d-nozzle
    id: icon_3dprinter
    resize: 100x100
    type: BINARY
    transparency: chroma_key
  - file: mdi:printer-3d-nozzle
    id: icon_3dprinter_mini
    resize: 60x60
    type: BINARY
    transparency: chroma_key
  - file: mdi:printer-3d-nozzle
    id: icon_3dprinter_hotend
    resize: 40x40
    type: BINARY
    transparency: chroma_key
  - file: mdi:square
    id: icon_3dprinter_bed
    resize: 40x40
    type: BINARY
    transparency: chroma_key

  ## Devices
  ## Dispositivos

  - file: ${image_3dprinter}
    id: img_3dprinter
    resize: 500x500
    type: RGB565
    transparency: alpha_channel
  - file: ${image_vacuum}
    id: img_vacuum
    resize: 430x430
    type: RGB565
    transparency: alpha_channel

interval:
  - interval: 60s
    then:
      - lvgl.label.update:
          id: lbl_clock
          text: !lambda 'return id(esptime).now().strftime("%H:%M");'
  - interval: 10s
    then:
      - if:
          condition:
            lambda: 'return !id(notification);'
          then:
            - lvgl.image.update:
                id: animation_widget
                src: img_animation_iddle
            - delay: 1s
            - lvgl.image.update:
                id: animation_widget
                src: img_animation_active

## Calendar
## Calendario

  - interval: 5min
    then:
      - script.execute: refresh_calendar_widget

  - interval: 60s
    then:
      - lvgl.bar.update:
          id: lbl_home_calendar_progress_bar
          value: !lambda |-
            if (id(calendar_state).state != "on")
              return 0;
            if (!id(calendar_event_start).has_state() || !id(calendar_event_end).has_state())
              return 0;
            std::string start = id(calendar_event_start).state;
            std::string end_s = id(calendar_event_end).state;
            if (start.empty() || end_s.empty())
              return 0;
            int sy, sm, sd, sh, smin, ss, ey, em, ed, eh, emin, es;
            sscanf(start.c_str(), "%d-%d-%d %d:%d:%d", &sy, &sm, &sd, &sh, &smin, &ss);
            sscanf(end_s.c_str(), "%d-%d-%d %d:%d:%d", &ey, &em, &ed, &eh, &emin, &es);
            auto now = id(esptime).now();
            if (!now.is_valid()) return 0;
            int now_mins = now.hour * 60 + now.minute;
            int start_mins = sh * 60 + smin;
            int end_mins = eh * 60 + emin;
            if (now_mins < start_mins) return 0;
            if (now_mins >= end_mins) return 100;
            return (int)(((float)(now_mins - start_mins) / (end_mins - start_mins)) * 100.0f);
            
light:
  - platform: monochromatic
    output: backlight_pwm
    name: "Pantalla"
    id: backlight
    icon: mdi:tablet
    restore_mode: ALWAYS_ON

media_player:
  - platform: speaker
    name: "Habbit Desk PRO"
    id: speaker_player
    announcement_pipeline:
      speaker: tab5_speaker
      format: FLAC
      sample_rate: 48000
      num_channels: 1
    on_announcement:
      # Stop the wake word (mWW or VA) if the mic is capturing
      - if:
          condition:
            - microphone.is_capturing:
          then:
            - micro_wake_word.stop:
    on_idle:
      # Since VA isn't running, this is the end of user-intiated media playback. Restart the wake word.
      - if:
          condition:
            not:
              voice_assistant.is_running:
          then:
            - micro_wake_word.start:

microphone:
  - platform: i2s_audio
    id: tab5_microphone
    i2s_din_pin: GPIO28
    sample_rate: 16000
    bits_per_sample: 16bit
    adc_type: external

micro_wake_word:
  id: mww
  models:
    - okay_nabu
#    - hey_mycroft
#    - hey_jarvis
  on_wake_word_detected:
    - voice_assistant.start:
        wake_word: !lambda return wake_word;
    - lvgl.page.show: page_assistant
    - light.turn_on: backlight
    - lvgl.resume:
    - lvgl.widget.redraw:

output:
  - platform: ledc
    pin: GPIO22
    id: backlight_pwm
    frequency: 1000Hz

pi4ioe5v6408:
  - id: pi4ioe1
    address: 0x43
    # 0: O - wifi_antenna_int_ext
    # 1: O - speaker_enable
    # 2: O - external_5v_power
    # 3: NC
    # 4: O - lcd reset
    # 5: O - touch panel reset
    # 6: O - camera reset
    # 7: I - headphone detect
  - id: pi4ioe2
    address: 0x44
    # 0: O - wifi_power
    # 1: NC
    # 2: NC
    # 3: O - usb_5v_power
    # 4: O - poweroff pulse
    # 5: O - quick charge enable (inverted)
    # 6: I - charging status
    # 7: O - charge enable

script:
  - id: refresh_calendar_widget
    then:
      - lvgl.image.update:
          id: icon_calendar_mini_id
          image_recolor: !lambda |-
            if (!id(calendar_event_start).has_state()) return lv_color_hex(0xFFFFFF);

            std::string start = id(calendar_event_start).state;
            if (start.empty()) return lv_color_hex(0xFFFFFF);

            int sy, sm, sd, sh, smin, ss;
            if (sscanf(start.c_str(), "%d-%d-%d %d:%d:%d", &sy, &sm, &sd, &sh, &smin, &ss) != 6) {
              return lv_color_hex(0xFFFFFF);
            }

            auto now = id(esptime).now();
            if (!now.is_valid()) return lv_color_hex(0xFFFFFF);

            bool is_today = (now.year == sy && now.month == sm && now.day_of_month == sd);
            bool calendar_off = (id(calendar_state).state == "off");

            if (is_today && calendar_off) return lv_color_hex(0x1EBBD7);
            return lv_color_hex(0xFFFFFF);
      - lvgl.label.update:
          id: lbl_home_calendar_event_name
          text: !lambda |-
            if (!id(calendar_event_name).has_state()) return std::string("Sin eventos");
            std::string s = id(calendar_event_name).state;
            if (s.length() > 40) return (s.substr(0, 37) + "...").c_str();
            return s.c_str();
          text_color: !lambda |-
            if (!id(calendar_event_start).has_state()) return lv_color_hex(0xFFFFFF);

            std::string start = id(calendar_event_start).state;
            if (start.empty()) return lv_color_hex(0xFFFFFF);

            int sy, sm, sd, sh, smin, ss;
            if (sscanf(start.c_str(), "%d-%d-%d %d:%d:%d", &sy, &sm, &sd, &sh, &smin, &ss) != 6) {
              return lv_color_hex(0xFFFFFF);
            }

            auto now = id(esptime).now();
            if (!now.is_valid()) return lv_color_hex(0xFFFFFF);

            bool is_today = (now.year == sy && now.month == sm && now.day_of_month == sd);
            bool calendar_off = (id(calendar_state).state == "off");

            if (is_today && calendar_off) return lv_color_hex(0x1EBBD7);
            return lv_color_hex(0xFFFFFF);
      - lvgl.label.update:
          id: lbl_home_calendar_event_details
          text: !lambda |-
            if (!id(calendar_event_start).has_state() || !id(calendar_event_end).has_state())
              return std::string("Sin eventos");
            std::string start = id(calendar_event_start).state;
            std::string end_s = id(calendar_event_end).state;
            if (start.empty() || end_s.empty()) return std::string("Sin eventos");
            int sy, sm, sd, sh, smin, ss, ey, em, ed, eh, emin, es;
            sscanf(start.c_str(), "%d-%d-%d %d:%d:%d", &sy, &sm, &sd, &sh, &smin, &ss);
            sscanf(end_s.c_str(), "%d-%d-%d %d:%d:%d", &ey, &em, &ed, &eh, &emin, &es);
            static const int t[] = {0, 3, 2, 5, 0, 3, 5, 1, 4, 6, 2, 4};
            int yr = sy; if (sm < 3) yr--;
            int dow = (yr + yr/4 - yr/100 + yr/400 + t[sm-1] + sd) % 7;
            const char* dias[] = {"Dom", "Lun", "Mar", "Mie", "Jue", "Vie", "Sab"};
            char buf[35];
            sprintf(buf, "%s %02d/%02d/%02d, %02d:%02d-%02d:%02d",
              dias[dow], sd, sm, sy % 100, sh, smin, eh, emin);
            return std::string(buf);
          text_color: !lambda |-
            if (!id(calendar_event_start).has_state()) return lv_color_hex(0xFFFFFF);

            std::string start = id(calendar_event_start).state;
            if (start.empty()) return lv_color_hex(0xFFFFFF);

            int sy, sm, sd, sh, smin, ss;
            if (sscanf(start.c_str(), "%d-%d-%d %d:%d:%d", &sy, &sm, &sd, &sh, &smin, &ss) != 6) {
              return lv_color_hex(0xFFFFFF);
            }

            auto now = id(esptime).now();
            if (!now.is_valid()) return lv_color_hex(0xFFFFFF);

            bool is_today = (now.year == sy && now.month == sm && now.day_of_month == sd);
            bool calendar_off = (id(calendar_state).state == "off");

            if (is_today && calendar_off) return lv_color_hex(0x1EBBD7);
            return lv_color_hex(0xFFFFFF);

select:
  - platform: template
    id: wifi_antenna_select
    name: "WiFi Antenna"
    internal: True
    options:
      - "Internal"
      - "External"
    optimistic: true
    on_value:
      - if:
          condition:
            lambda: return i == 0;
          then:
            - switch.turn_off: wifi_antenna_int_ext
          else:
            - switch.turn_on: wifi_antenna_int_ext

  ## The DAC Output select needs to be manually (or with an automation) changed to `LINE1` for the onboard speaker
  - platform: es8388
    es8388_id: es8388_dac
    dac_output:
      name: DAC Output
      id: dac_output_select
    adc_input_mic:
      name: ADC Input Mic

sensor:

# Built-in sensors
# Sensores internos

  - platform: wifi_signal
    name: "Intensidad WiFi"
    update_interval: 300s
    on_value:
      then:
      - lvgl.label.update:
          id: wifi_icon
          text: !lambda |-
            if (std::isnan(x)) return "\U0000e648";  // wifi off - sin conexión
            return "\U0000e63e";  // wifi on
          text_color: !lambda |-
            if (std::isnan(x)) return lv_color_hex(0x767676);  // wifi off - sin conexión
            return lv_color_hex(0xffffff);    // wifi on

  - platform: ina226
    id: ina226_sensor
    address: 0x41
    adc_averaging: 16
    max_current: 8.192A
    shunt_resistance: 0.005ohm
    bus_voltage:
      name: Battery Voltage
      id: battery_voltage
      internal: true
    current:
      name: Battery Current
      internal: true

  - platform: template
    name: "Batería"
    id: battery_percentage
    unit_of_measurement: "%"
    accuracy_decimals: 0
    device_class: battery
    state_class: measurement
    lambda: |-
      // Define los voltajes mínimo y máximo de tu batería
      float voltage_max = 8.23;  // Voltaje máximo (batería llena)
      float voltage_min = 6.0;  // Voltaje mínimo (batería vacía)
      
      float voltage = id(battery_voltage).state;
      
      // Calcula el porcentaje
      float percentage = (voltage - voltage_min) / (voltage_max - voltage_min) * 100.0;
      
      // Limita el porcentaje entre 0 y 100
      if (percentage > 100.0) percentage = 100.0;
      if (percentage < 0.0) percentage = 0.0;
      
      return percentage;
    update_interval: 600s  
    on_value:
      then:
      - lvgl.label.update:
          id: lbl_battery_percentage
          text:
            format: "%.0f%%"
            args:
              - id(battery_percentage).state
      - lvgl.label.update:
          id: battery_icon
          text: !lambda |-
            if (id(charging).state) return "\U0000f250";  
            float pct = x;
            if (pct >= 70.0) return "\U0000f24f";
            if (pct >= 30.0) return "\U0000f255";
            return "\U0000f257";
          text_color: !lambda |-
            float pct = id(battery_percentage).state;
            if (std::isnan(pct)) return lv_color_hex(0x767676);
            if (id(charging).state) return lv_color_hex(0xe6b854);
            if (pct >= 70.0) return lv_color_hex(0xc9e654);
            if (pct >= 30.0) return lv_color_hex(0xe65476);
            return lv_color_hex(0x767676);

  ## Climate
  ## Climatización

  - platform: homeassistant
    id: room_temperature_value
    entity_id: ${room_temperature}
    internal: true
    on_value:
      then:
      - lvgl.label.update:
          id: lbl_room_temperature
          text:
            format: "%.0f°C"
            args:
              - id(room_temperature_value).state
          text_color: !lambda |-
            float t = id(room_temperature_value).state;
            if (t < 18.0) return lv_color_hex(0x1e9cd7); 
            if (t > 25.0) return lv_color_hex(0xe65476);  
            return lv_color_hex(0x626262);                 
      - lvgl.label.update:
          id: thermometer_icon
          text_color: !lambda |-
            float t = id(room_temperature_value).state;
            if (t < 18.0) return lv_color_hex(0x1e9cd7); 
            if (t > 25.0) return lv_color_hex(0xe65476);  
            return lv_color_hex(0x626262);                 
      - if:
          condition:
            lambda: 'return id(room_temperature_value).state < 23.0;'
          then:
            - lvgl.widget.show: btn_climate
            - lvgl.widget.hide: btn_fan
          else:
            - lvgl.widget.hide: btn_climate
            - lvgl.widget.show: btn_fan

  - platform: homeassistant
    id: room_humidity_value
    entity_id: ${room_humidity}
    internal: true
    on_value:
      then:
      - lvgl.label.update:
          id: lbl_room_humidity
          text:
            format: "%.0f%%"
            args:
              - id(room_humidity_value).state
          text_color: !lambda |-
            float t = id(room_humidity_value).state;
            if (t < 40.0) return lv_color_hex(0xe0a020); 
            if (t > 60.0) return lv_color_hex(0x1e9cd7);  
            return lv_color_hex(0x626262);                 
      - lvgl.label.update:
          id: humidity_icon
          text_color: !lambda |-
            float t = id(room_humidity_value).state;
            if (t < 40.0) return lv_color_hex(0xe0a020); 
            if (t > 60.0) return lv_color_hex(0x1e9cd7);  
            return lv_color_hex(0x626262);                 

  ## Media
  ## Media

  - platform: homeassistant
    id: media_duration
    entity_id: ${media}
    attribute: media_duration
    internal: true
    
  - platform: homeassistant
    id: media_position
    entity_id: ${media}
    attribute: media_position
    internal: true
    on_value:
      then:
        - lvgl.bar.update:
            id: lbl_home_media_progress_bar
            value: !lambda |-
              float dur = id(media_duration).state;
              float pos = id(media_position).state;
              if (dur <= 0) return 0;
              return (int)((pos / dur) * 100.0f);

# Devices
# Dispositivos

  # YOUTUBE

  - platform: homeassistant
    id: goal_current_value
    entity_id: ${goal_current}
    internal: true
    filters:
      - lambda: |-
          if (isnan(x)) return 0;
          return x;
    on_value:  
      then:
        - lvgl.bar.update:
            id: lbl_home_goal_progress_bar
            value: !lambda return (int)x;
        - lvgl.label.update:
            id: lbl_home_goal_current
            text: !lambda |-
              char buf[30];
              snprintf(buf, sizeof(buf), "%.0f Suscriptores", x);
              return std::string(buf);
        - lvgl.label.update:
            id: lbl_home_goal_progress_value
            text: !lambda |-
              int pct = (int)(x / 3000.0f * 100.0f);
              char buf[10];
              snprintf(buf, sizeof(buf), "%d%%", pct);
              return std::string(buf);

  # COVER

  - platform: homeassistant
    id: cover_1_position_value
    entity_id: ${cover1}
    attribute: current_position
    internal: true
    filters:
      - lambda: |-
...

This file has been truncated, please download it to see its full contents.

Credits

AguacaTEC Team
1 project • 0 followers

Comments