Refactor integration health monitor automation, remove litter box monitor package, update lunch menus formatting, delete voice satellite alarms, and remove litter box analyzer scripts
This commit is contained in:
@@ -1,47 +0,0 @@
|
||||
#include "esphome.h"
|
||||
|
||||
#define MAX17048_ADDRESS 0x36
|
||||
#define MAX17048_VCELL 0x02 // voltage
|
||||
#define MAX17048_SOC 0x04 // percentage
|
||||
#define MAX17048_MODE 0x06
|
||||
#define MAX17048_VERSION 0x08
|
||||
#define MAX17048_CONFIG 0x0c
|
||||
#define MAX17048_COMMAND 0xfe
|
||||
|
||||
class MAX17048Sensor : public PollingComponent, public Sensor {
|
||||
public:
|
||||
Sensor *voltage_sensor = new Sensor();
|
||||
Sensor *percentage_sensor = new Sensor();
|
||||
|
||||
MAX17048Sensor() : PollingComponent(10000) {}
|
||||
|
||||
void setup() override {
|
||||
// Initialize the device here. Usually Wire.begin() will be called in here,
|
||||
// though that call is unnecessary if you have an 'i2c:' entry in your config
|
||||
ESP_LOGD("custom", "Starting up MAX17048 sensor");
|
||||
|
||||
Wire.begin();
|
||||
}
|
||||
|
||||
uint16_t read16(uint8_t reg) {
|
||||
uint16_t temp;
|
||||
Wire.begin();
|
||||
Wire.beginTransmission(MAX17048_ADDRESS);
|
||||
Wire.write(reg);
|
||||
Wire.endTransmission();
|
||||
Wire.requestFrom(MAX17048_ADDRESS, 2);
|
||||
temp = (uint16_t)Wire.read() << 8;
|
||||
temp |= (uint16_t)Wire.read();
|
||||
Wire.endTransmission();
|
||||
return temp;
|
||||
}
|
||||
|
||||
void update() override {
|
||||
float voltage = (float)(read16(MAX17048_VCELL)) * 78.125 / 1000000;
|
||||
voltage_sensor->publish_state(voltage);
|
||||
|
||||
uint16_t percentage_tmp = read16(MAX17048_SOC);
|
||||
float percentage = (float)(percentage_tmp) / 256;
|
||||
percentage_sensor->publish_state(percentage);
|
||||
}
|
||||
};
|
||||
+1369
-1369
File diff suppressed because it is too large
Load Diff
@@ -1,115 +1,115 @@
|
||||
################################################################################
|
||||
# chore-tracker-dashboard.yaml (AUTO-GENERATED — edit chores_config.yaml)
|
||||
# Kids: Jordyn, Declan, Chloe
|
||||
################################################################################
|
||||
# ################################################################################
|
||||
# # chore-tracker-dashboard.yaml (AUTO-GENERATED — edit chores_config.yaml)
|
||||
# # Kids: Jordyn, Declan, Chloe
|
||||
# ################################################################################
|
||||
|
||||
title: "Chore Tracker"
|
||||
views:
|
||||
- title: Today
|
||||
icon: mdi:checkbox-marked-circle
|
||||
path: chores
|
||||
cards:
|
||||
- type: horizontal-stack
|
||||
cards:
|
||||
- type: markdown
|
||||
content: >
|
||||
### Jordyn
|
||||
{{
|
||||
'All done!' if states('sensor.jordyn_all_chores_done') == 'True'
|
||||
else states('sensor.jordyn_chores_done_today') ~ '/4 chores done'
|
||||
}}
|
||||
# title: "Chore Tracker"
|
||||
# views:
|
||||
# - title: Today
|
||||
# icon: mdi:checkbox-marked-circle
|
||||
# path: chores
|
||||
# cards:
|
||||
# - type: horizontal-stack
|
||||
# cards:
|
||||
# - type: markdown
|
||||
# content: >
|
||||
# ### Jordyn
|
||||
# {{
|
||||
# 'All done!' if states('sensor.jordyn_all_chores_done') == 'True'
|
||||
# else states('sensor.jordyn_chores_done_today') ~ '/4 chores done'
|
||||
# }}
|
||||
|
||||
- type: markdown
|
||||
content: >
|
||||
### Declan
|
||||
{{
|
||||
'All done!' if states('sensor.declan_all_chores_done') == 'True'
|
||||
else states('sensor.declan_chores_done_today') ~ '/2 chores done'
|
||||
}}
|
||||
# - type: markdown
|
||||
# content: >
|
||||
# ### Declan
|
||||
# {{
|
||||
# 'All done!' if states('sensor.declan_all_chores_done') == 'True'
|
||||
# else states('sensor.declan_chores_done_today') ~ '/2 chores done'
|
||||
# }}
|
||||
|
||||
- type: markdown
|
||||
content: >
|
||||
### Chloe
|
||||
{{
|
||||
'All done!' if states('sensor.chloe_all_chores_done') == 'True'
|
||||
else states('sensor.chloe_chores_done_today') ~ '/3 chores done'
|
||||
}}
|
||||
# - type: markdown
|
||||
# content: >
|
||||
# ### Chloe
|
||||
# {{
|
||||
# 'All done!' if states('sensor.chloe_all_chores_done') == 'True'
|
||||
# else states('sensor.chloe_chores_done_today') ~ '/3 chores done'
|
||||
# }}
|
||||
|
||||
- type: button
|
||||
name: "Reset ALL Chores"
|
||||
icon: mdi:restart-alert
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: input_button.press
|
||||
target:
|
||||
entity_id: input_button.reset_all_chores
|
||||
- type: entities
|
||||
title: " Jordyn's Chores"
|
||||
entities:
|
||||
- entity: switch.chore_tracker_jordyn_put_on_underwear
|
||||
name: " Put on underwear"
|
||||
# - type: button
|
||||
# name: "Reset ALL Chores"
|
||||
# icon: mdi:restart-alert
|
||||
# tap_action:
|
||||
# action: call-service
|
||||
# service: input_button.press
|
||||
# target:
|
||||
# entity_id: input_button.reset_all_chores
|
||||
# - type: entities
|
||||
# title: " Jordyn's Chores"
|
||||
# entities:
|
||||
# - entity: switch.chore_tracker_jordyn_put_on_underwear
|
||||
# name: " Put on underwear"
|
||||
|
||||
- entity: switch.chore_tracker_jordyn_brush_teeth
|
||||
name: " Brush Teeth"
|
||||
# - entity: switch.chore_tracker_jordyn_brush_teeth
|
||||
# name: " Brush Teeth"
|
||||
|
||||
- entity: switch.chore_tracker_jordyn_fill_water_bowls
|
||||
name: " Fill water bowls"
|
||||
# - entity: switch.chore_tracker_jordyn_fill_water_bowls
|
||||
# name: " Fill water bowls"
|
||||
|
||||
- entity: switch.chore_tracker_jordyn_restock_cat_food_cans
|
||||
name: " Restock Cat Food Cans"
|
||||
- type: divider
|
||||
- entity: sensor.jordyn_chores_done_today
|
||||
name: "Chores done today"
|
||||
- entity: sensor.jordyn_all_chores_done
|
||||
name: "All Done?"
|
||||
- type: button
|
||||
name: "Reset Jordyn's Chores"
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: input_button.press
|
||||
target:
|
||||
entity_id: input_button.jordyn_reset_chores
|
||||
- type: entities
|
||||
title: " Declan's Chores"
|
||||
entities:
|
||||
- entity: switch.chore_tracker_declan_take_morning_pill
|
||||
name: " Take morning pill"
|
||||
# - entity: switch.chore_tracker_jordyn_restock_cat_food_cans
|
||||
# name: " Restock Cat Food Cans"
|
||||
# - type: divider
|
||||
# - entity: sensor.jordyn_chores_done_today
|
||||
# name: "Chores done today"
|
||||
# - entity: sensor.jordyn_all_chores_done
|
||||
# name: "All Done?"
|
||||
# - type: button
|
||||
# name: "Reset Jordyn's Chores"
|
||||
# tap_action:
|
||||
# action: call-service
|
||||
# service: input_button.press
|
||||
# target:
|
||||
# entity_id: input_button.jordyn_reset_chores
|
||||
# - type: entities
|
||||
# title: " Declan's Chores"
|
||||
# entities:
|
||||
# - entity: switch.chore_tracker_declan_take_morning_pill
|
||||
# name: " Take morning pill"
|
||||
|
||||
- entity: switch.chore_tracker_declan_scoop_dog_poop
|
||||
name: " Scoop Dog Poop"
|
||||
- type: divider
|
||||
- entity: sensor.declan_chores_done_today
|
||||
name: "Chores done today"
|
||||
- entity: sensor.declan_all_chores_done
|
||||
name: "All Done?"
|
||||
- type: button
|
||||
name: "Reset Declan's Chores"
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: input_button.press
|
||||
target:
|
||||
entity_id: input_button.declan_reset_chores
|
||||
- type: entities
|
||||
title: " Chloe's Chores"
|
||||
entities:
|
||||
- entity: switch.chore_tracker_chloe_fill_kitty_feeders
|
||||
name: " Fill kitty feeders"
|
||||
# - entity: switch.chore_tracker_declan_scoop_dog_poop
|
||||
# name: " Scoop Dog Poop"
|
||||
# - type: divider
|
||||
# - entity: sensor.declan_chores_done_today
|
||||
# name: "Chores done today"
|
||||
# - entity: sensor.declan_all_chores_done
|
||||
# name: "All Done?"
|
||||
# - type: button
|
||||
# name: "Reset Declan's Chores"
|
||||
# tap_action:
|
||||
# action: call-service
|
||||
# service: input_button.press
|
||||
# target:
|
||||
# entity_id: input_button.declan_reset_chores
|
||||
# - type: entities
|
||||
# title: " Chloe's Chores"
|
||||
# entities:
|
||||
# - entity: switch.chore_tracker_chloe_fill_kitty_feeders
|
||||
# name: " Fill kitty feeders"
|
||||
|
||||
- entity: switch.chore_tracker_chloe_scoop_kitty_litter
|
||||
name: " Scoop Kitty Litter"
|
||||
# - entity: switch.chore_tracker_chloe_scoop_kitty_litter
|
||||
# name: " Scoop Kitty Litter"
|
||||
|
||||
- entity: switch.chore_tracker_chloe_replace_kitty_litter_bags
|
||||
name: " Replace Kitty Litter Bags"
|
||||
- type: divider
|
||||
- entity: sensor.chloe_chores_done_today
|
||||
name: "Chores done today"
|
||||
- entity: sensor.chloe_all_chores_done
|
||||
name: "All Done?"
|
||||
- type: button
|
||||
name: "Reset Chloe's Chores"
|
||||
tap_action:
|
||||
action: call-service
|
||||
service: input_button.press
|
||||
target:
|
||||
entity_id: input_button.chloe_reset_chores
|
||||
# - entity: switch.chore_tracker_chloe_replace_kitty_litter_bags
|
||||
# name: " Replace Kitty Litter Bags"
|
||||
# - type: divider
|
||||
# - entity: sensor.chloe_chores_done_today
|
||||
# name: "Chores done today"
|
||||
# - entity: sensor.chloe_all_chores_done
|
||||
# name: "All Done?"
|
||||
# - type: button
|
||||
# name: "Reset Chloe's Chores"
|
||||
# tap_action:
|
||||
# action: call-service
|
||||
# service: input_button.press
|
||||
# target:
|
||||
# entity_id: input_button.chloe_reset_chores
|
||||
|
||||
|
||||
@@ -1,81 +1,81 @@
|
||||
################################################################################
|
||||
# chores_config.yaml — YOUR SINGLE SOURCE OF TRUTH
|
||||
#
|
||||
# Edit this file to change kids, chores, and settings.
|
||||
# Then run: python3 generate.py
|
||||
#
|
||||
# ICONS: Use MDI codepoints from https://pictogrammers.com/library/mdi/
|
||||
# Format: "\U000FXXXX" e.g. mdi:bed = "\U000F02E3"
|
||||
# The kid avatar field also uses MDI codepoints.
|
||||
#
|
||||
# Common chore icons:
|
||||
# Bed: "\U000F02E3" mdi:bed
|
||||
# Brush teeth: "\U000F09A9" mdi:toothbrush
|
||||
# Broom: "\U000F00A8" mdi:broom
|
||||
# Book: "\U000F0219" mdi:book-open-variant
|
||||
# Dog: "\U000F01F9" mdi:dog
|
||||
# Paw: "\U000F0265" mdi:paw
|
||||
# Silverware: "\U000F03E7" mdi:silverware-fork-knife
|
||||
# Trash: "\U000F05B8" mdi:trash-can
|
||||
# Watering can: "\U000F1B25" mdi:watering-can
|
||||
# Piano: "\U000F0F9E" mdi:piano
|
||||
# Dumbbell: "\U000F0F1E" mdi:dumbbell
|
||||
# Shower: "\U000F0467" mdi:shower
|
||||
################################################################################
|
||||
# ################################################################################
|
||||
# # chores_config.yaml — YOUR SINGLE SOURCE OF TRUTH
|
||||
# #
|
||||
# # Edit this file to change kids, chores, and settings.
|
||||
# # Then run: python3 generate.py
|
||||
# #
|
||||
# # ICONS: Use MDI codepoints from https://pictogrammers.com/library/mdi/
|
||||
# # Format: "\U000FXXXX" e.g. mdi:bed = "\U000F02E3"
|
||||
# # The kid avatar field also uses MDI codepoints.
|
||||
# #
|
||||
# # Common chore icons:
|
||||
# # Bed: "\U000F02E3" mdi:bed
|
||||
# # Brush teeth: "\U000F09A9" mdi:toothbrush
|
||||
# # Broom: "\U000F00A8" mdi:broom
|
||||
# # Book: "\U000F0219" mdi:book-open-variant
|
||||
# # Dog: "\U000F01F9" mdi:dog
|
||||
# # Paw: "\U000F0265" mdi:paw
|
||||
# # Silverware: "\U000F03E7" mdi:silverware-fork-knife
|
||||
# # Trash: "\U000F05B8" mdi:trash-can
|
||||
# # Watering can: "\U000F1B25" mdi:watering-can
|
||||
# # Piano: "\U000F0F9E" mdi:piano
|
||||
# # Dumbbell: "\U000F0F1E" mdi:dumbbell
|
||||
# # Shower: "\U000F0467" mdi:shower
|
||||
# ################################################################################
|
||||
|
||||
settings:
|
||||
device_name: chore-tracker
|
||||
friendly_name: "Chore Tracker"
|
||||
wifi_ssid: !secret wifi_iot_ssid
|
||||
wifi_password: !secret wifi_password
|
||||
api_key: !secret api_encryption_key
|
||||
ota_password: !secret ota_password
|
||||
reset_time: "00:00:00"
|
||||
reminder_time: "18:00"
|
||||
notify_service: notify.notify
|
||||
static_ip: 192.168.69.115
|
||||
gateway: 192.168.69.1
|
||||
subnet: 255.255.255.0
|
||||
# settings:
|
||||
# device_name: chore-tracker
|
||||
# friendly_name: "Chore Tracker"
|
||||
# wifi_ssid: !secret wifi_iot_ssid
|
||||
# wifi_password: !secret wifi_password
|
||||
# api_key: !secret api_encryption_key
|
||||
# ota_password: !secret ota_password
|
||||
# reset_time: "00:00:00"
|
||||
# reminder_time: "18:00"
|
||||
# notify_service: notify.notify
|
||||
# static_ip: 192.168.69.115
|
||||
# gateway: 192.168.69.1
|
||||
# subnet: 255.255.255.0
|
||||
|
||||
|
||||
|
||||
# ── KIDS ──────────────────────────────────────────────────────────────────────
|
||||
# avatar: MDI codepoint shown in the sidebar on the kid's chore page
|
||||
# color / color_dark: hex colours for that kid's sidebar
|
||||
#
|
||||
kids:
|
||||
- name: Jordyn
|
||||
avatar: "\U000F011B" # mdi:cat
|
||||
color: "5F5980" # #5F5980 is a nice light blue, but it's a bit hard to read when used as a background colour for the sidebar. So I use a darker version of the same colour for the sidebar background.
|
||||
color_dark: "2F3061" # #2F3061 is a darker version of #5F5980, and is easier to read when used as a background colour for the sidebar.
|
||||
chores:
|
||||
- name: Put on underwear
|
||||
icon: "\U000F02E3" # mdi:bed
|
||||
- name: Brush Teeth
|
||||
icon: "\U000F09A9" # mdi:toothbrush
|
||||
- name: Fill water bowls
|
||||
icon: "\U000F01AB" # mdi:cup-water
|
||||
- name: Restock Cat Food Cans
|
||||
icon: "\U000F171C" # mdi:food-turkey
|
||||
# # ── KIDS ──────────────────────────────────────────────────────────────────────
|
||||
# # avatar: MDI codepoint shown in the sidebar on the kid's chore page
|
||||
# # color / color_dark: hex colours for that kid's sidebar
|
||||
# #
|
||||
# kids:
|
||||
# - name: Jordyn
|
||||
# avatar: "\U000F011B" # mdi:cat
|
||||
# color: "5F5980" # #5F5980 is a nice light blue, but it's a bit hard to read when used as a background colour for the sidebar. So I use a darker version of the same colour for the sidebar background.
|
||||
# color_dark: "2F3061" # #2F3061 is a darker version of #5F5980, and is easier to read when used as a background colour for the sidebar.
|
||||
# chores:
|
||||
# - name: Put on underwear
|
||||
# icon: "\U000F02E3" # mdi:bed
|
||||
# - name: Brush Teeth
|
||||
# icon: "\U000F09A9" # mdi:toothbrush
|
||||
# - name: Fill water bowls
|
||||
# icon: "\U000F01AB" # mdi:cup-water
|
||||
# - name: Restock Cat Food Cans
|
||||
# icon: "\U000F171C" # mdi:food-turkey
|
||||
|
||||
- name: Declan
|
||||
avatar: "\U000F1477" # mdi:wizard-hat
|
||||
color: "720026" # #720026 is a deep red, used for the kid's sidebar background.
|
||||
color_dark: "4F000B" # #4F000B is a darker version of #720026, easier to read when used as a background colour for the sidebar.
|
||||
chores:
|
||||
- name: Take morning pill
|
||||
icon: "\U000F0402" # mdi:pill
|
||||
- name: Scoop Dog Poop
|
||||
icon: "\U000F01F7" # mdi:emoticon-poop
|
||||
# - name: Declan
|
||||
# avatar: "\U000F1477" # mdi:wizard-hat
|
||||
# color: "720026" # #720026 is a deep red, used for the kid's sidebar background.
|
||||
# color_dark: "4F000B" # #4F000B is a darker version of #720026, easier to read when used as a background colour for the sidebar.
|
||||
# chores:
|
||||
# - name: Take morning pill
|
||||
# icon: "\U000F0402" # mdi:pill
|
||||
# - name: Scoop Dog Poop
|
||||
# icon: "\U000F01F7" # mdi:emoticon-poop
|
||||
|
||||
- name: Chloe
|
||||
avatar: "\U000F16A3" # mdi:robot-excited
|
||||
color: "73683B" # #73683B is a light brown, used for the kid's sidebar background.
|
||||
color_dark: "583E23" # #583E23 is a darker version of #73683B, easier to read when used as a background colour for the sidebar.
|
||||
chores:
|
||||
- name: Fill kitty feeders
|
||||
icon: "\U000F02A9" # mdi:bowl-outline
|
||||
- name: Scoop Kitty Litter
|
||||
icon: "\U000F01F7" # mdi:emoticon-poop
|
||||
- name: Replace Kitty Litter Bags
|
||||
icon: "\U000F01F7" # mdi:emoticon-poop
|
||||
# - name: Chloe
|
||||
# avatar: "\U000F16A3" # mdi:robot-excited
|
||||
# color: "73683B" # #73683B is a light brown, used for the kid's sidebar background.
|
||||
# color_dark: "583E23" # #583E23 is a darker version of #73683B, easier to read when used as a background colour for the sidebar.
|
||||
# chores:
|
||||
# - name: Fill kitty feeders
|
||||
# icon: "\U000F02A9" # mdi:bowl-outline
|
||||
# - name: Scoop Kitty Litter
|
||||
# icon: "\U000F01F7" # mdi:emoticon-poop
|
||||
# - name: Replace Kitty Litter Bags
|
||||
# icon: "\U000F01F7" # mdi:emoticon-poop
|
||||
+989
-989
File diff suppressed because it is too large
Load Diff
@@ -1 +0,0 @@
|
||||
CODEOWNERS = ["@blacknell"]
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
#include "esphome/core/automation.h"
|
||||
#include "max17043.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max17043 {
|
||||
|
||||
template<typename... Ts> class SleepAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SleepAction(MAX17043Component *max17043) : max17043_(max17043) {}
|
||||
|
||||
void play(Ts... x) override { this->max17043_->sleep_mode(); }
|
||||
|
||||
protected:
|
||||
MAX17043Component *max17043_;
|
||||
};
|
||||
|
||||
} // namespace max17043
|
||||
} // namespace esphome
|
||||
@@ -1,96 +0,0 @@
|
||||
#include "max17043.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max17043 {
|
||||
|
||||
// MAX174043 is a 1-Cell Fuel Gauge with ModelGauge and Low-Battery Alert
|
||||
// Consult the datasheet at https://www.analog.com/en/products/max17043.html
|
||||
|
||||
static const char *const TAG = "max17043";
|
||||
|
||||
static const uint8_t MAX17043_VCELL = 0x02;
|
||||
static const uint8_t MAX17043_SOC = 0x04;
|
||||
static const uint8_t MAX17043_CONFIG = 0x0c;
|
||||
|
||||
static const uint16_t MAX17043_CONFIG_POWER_UP_DEFAULT = 0x971C;
|
||||
static const uint16_t MAX17043_CONFIG_SAFE_MASK = 0xFF1F; // mask out sleep bit (7), unused bit (6) and alert bit (4)
|
||||
static const uint16_t MAX17043_CONFIG_SLEEP_MASK = 0x0080;
|
||||
|
||||
void MAX17043Component::update() {
|
||||
uint16_t raw_voltage, raw_percent;
|
||||
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
if (!this->read_byte_16(MAX17043_VCELL, &raw_voltage)) {
|
||||
this->status_set_warning("Unable to read MAX17043_VCELL");
|
||||
} else {
|
||||
float voltage = (1.25 * (float) (raw_voltage >> 4)) / 1000.0;
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
if (this->battery_remaining_sensor_ != nullptr) {
|
||||
if (!this->read_byte_16(MAX17043_SOC, &raw_percent)) {
|
||||
this->status_set_warning("Unable to read MAX17043_SOC");
|
||||
} else {
|
||||
float percent = (float) ((raw_percent >> 8) + 0.003906f * (raw_percent & 0x00ff));
|
||||
this->battery_remaining_sensor_->publish_state(percent);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MAX17043Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MAX17043...");
|
||||
|
||||
uint16_t config_reg;
|
||||
if (this->write(&MAX17043_CONFIG, 1) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
if (this->read(reinterpret_cast<uint8_t *>(&config_reg), 2) != i2c::ERROR_OK) {
|
||||
this->status_set_warning();
|
||||
return;
|
||||
}
|
||||
|
||||
config_reg = i2c::i2ctohs(config_reg) & MAX17043_CONFIG_SAFE_MASK;
|
||||
ESP_LOGV(TAG, "MAX17043 CONFIG register reads 0x%X", config_reg);
|
||||
|
||||
if (config_reg != MAX17043_CONFIG_POWER_UP_DEFAULT) {
|
||||
ESP_LOGW(TAG, "Non-standard CONFIG register 0x%X at 0x36; continuing in compatible mode", config_reg);
|
||||
return;
|
||||
}
|
||||
|
||||
// need to write back to config register to reset the sleep bit
|
||||
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT)) {
|
||||
this->status_set_error("sleep reset failed");
|
||||
this->mark_failed();
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
void MAX17043Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MAX17043:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with MAX17043 failed");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_remaining_sensor_);
|
||||
}
|
||||
|
||||
float MAX17043Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void MAX17043Component::sleep_mode() {
|
||||
if (!this->is_failed()) {
|
||||
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT | MAX17043_CONFIG_SLEEP_MASK)) {
|
||||
ESP_LOGW(TAG, "Unable to write the sleep bit to config register");
|
||||
this->status_set_warning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace max17043
|
||||
} // namespace esphome
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max17043 {
|
||||
|
||||
class MAX17043Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void sleep_mode();
|
||||
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_battery_remaining_sensor(sensor::Sensor *battery_remaining_sensor) {
|
||||
battery_remaining_sensor_ = battery_remaining_sensor;
|
||||
}
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *battery_remaining_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace max17043
|
||||
} // namespace esphome
|
||||
@@ -1,77 +0,0 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
max17043_ns = cg.esphome_ns.namespace("max17043")
|
||||
MAX17043Component = max17043_ns.class_(
|
||||
"MAX17043Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
# Actions
|
||||
SleepAction = max17043_ns.class_("SleepAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MAX17043Component),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x36))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery_remaining_sensor(sens))
|
||||
|
||||
|
||||
MAX17043_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(MAX17043Component),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("max17043.sleep_mode", SleepAction, MAX17043_ACTION_SCHEMA)
|
||||
async def max17043_sleep_mode_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
@@ -1,122 +0,0 @@
|
||||
substitutions:
|
||||
device_name: declan-a1-camera
|
||||
friendly_name: "Declan's A1 Camera"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
manual_ip:
|
||||
static_ip: 192.168.69.220
|
||||
gateway: 192.168.69.1
|
||||
subnet: 255.255.255.0
|
||||
ap:
|
||||
ssid: "${friendly_name} Fallback"
|
||||
password: !secret fallback_password
|
||||
# Prevents aggressive reconnection attempts
|
||||
power_save_mode: none # Use 'light' for battery devices
|
||||
|
||||
# Slower but more reliable connection
|
||||
fast_connect: true
|
||||
|
||||
# Handle connection failures gracefully
|
||||
on_connect:
|
||||
- logger.log: "Wi-Fi connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Wi-Fi disconnected!"
|
||||
|
||||
# Reduce mDNS traffic
|
||||
mdns:
|
||||
disabled: true # Set to true if you use static IPs and don't need discovery
|
||||
|
||||
# Diagnostic sensors
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "${device_name} WiFi Signal"
|
||||
update_interval: 60s
|
||||
|
||||
- platform: uptime
|
||||
name: "${device_name} Uptime"
|
||||
update_interval: 60s
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "${device_name} IP Address"
|
||||
ssid:
|
||||
name: "${device_name} Connected SSID"
|
||||
bssid:
|
||||
name: "${device_name} BSSID"
|
||||
|
||||
# Watchdog to auto-reboot if things go wrong
|
||||
interval:
|
||||
- interval: 5min
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- lambda: 'return millis() > 300000;' # >5 min uptime
|
||||
- not:
|
||||
wifi.connected:
|
||||
then:
|
||||
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
|
||||
- delay: 10s
|
||||
- lambda: 'App.safe_reboot();'
|
||||
|
||||
captive_portal:
|
||||
|
||||
i2c:
|
||||
- id: camera_i2c
|
||||
sda: GPIO26
|
||||
scl: GPIO27
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
speed: 80MHz
|
||||
|
||||
esp32_camera:
|
||||
external_clock:
|
||||
pin: GPIO0
|
||||
frequency: 20MHz
|
||||
i2c_id: camera_i2c
|
||||
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
|
||||
vsync_pin: GPIO25
|
||||
href_pin: GPIO23
|
||||
pixel_clock_pin: GPIO22
|
||||
power_down_pin: GPIO32
|
||||
resolution: 1024x768
|
||||
jpeg_quality: 15
|
||||
vertical_flip: true
|
||||
horizontal_mirror: false
|
||||
max_framerate: 20 fps
|
||||
idle_framerate: 0.05 fps
|
||||
name: ${device_name}
|
||||
|
||||
esp32_camera_web_server:
|
||||
- port: 8080
|
||||
mode: STREAM
|
||||
- port: 8081
|
||||
mode: SNAPSHOT
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "${device_name}-flash"
|
||||
pin: 4
|
||||
@@ -60,7 +60,10 @@ esp32_camera:
|
||||
name: "${friendly_name}"
|
||||
external_clock:
|
||||
pin: GPIO5
|
||||
frequency: 20MHz
|
||||
# 10MHz (half the usual 20MHz) slows the sensor's line rate, so each of the
|
||||
# 1200 exposure lines below collects roughly twice the light. Costs
|
||||
# framerate, which does not matter for chamber monitoring.
|
||||
frequency: 10MHz
|
||||
i2c_pins:
|
||||
sda: GPIO8
|
||||
scl: GPIO9
|
||||
@@ -87,14 +90,17 @@ esp32_camera:
|
||||
contrast: -2
|
||||
saturation: 0
|
||||
|
||||
# Auto-Exposure & Gain tuning (AEC2 + AE Level lift shadows away from bright LEDs).
|
||||
# The gain ceiling is the main lever for the tinted door: 8X was capping how
|
||||
# far AGC could amplify, leaving everything outside the LED strips black.
|
||||
aec_mode: auto
|
||||
# Exposure & gain are MANUAL on purpose. The two LED strips in frame kept the
|
||||
# auto-exposure meter satisfied, so AEC never lengthened the exposure and the
|
||||
# tinted-door scene stayed black regardless of gain ceiling or AE level.
|
||||
# Pinning both to max forces a bright chamber; the strips clip out entirely.
|
||||
aec_mode: manual
|
||||
aec_value: 1200 # max integration time (0-1200 lines)
|
||||
aec2: true
|
||||
ae_level: 2
|
||||
agc_mode: auto
|
||||
agc_gain_ceiling: 128X
|
||||
ae_level: 2 # only consulted if AEC is flipped back to auto
|
||||
agc_mode: manual
|
||||
agc_value: 30 # max sensor gain (0-30)
|
||||
agc_gain_ceiling: 128X # only consulted if AGC is flipped back to auto
|
||||
wb_mode: auto
|
||||
|
||||
# Camera endpoints:
|
||||
@@ -113,6 +119,25 @@ switch:
|
||||
pin: GPIO47
|
||||
restore_mode: ALWAYS_OFF
|
||||
|
||||
# Flip exposure/gain back to automatic for A/B comparison without a reflash.
|
||||
# Off (the default) = manual exposure per the esp32_camera block above.
|
||||
- platform: template
|
||||
name: "${friendly_name} Auto Exposure"
|
||||
optimistic: true
|
||||
restore_mode: ALWAYS_OFF
|
||||
turn_on_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (!s) return;
|
||||
s->set_exposure_ctrl(s, 1);
|
||||
s->set_gain_ctrl(s, 1);
|
||||
turn_off_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (!s) return;
|
||||
s->set_exposure_ctrl(s, 0);
|
||||
s->set_gain_ctrl(s, 0);
|
||||
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "${friendly_name} WiFi Signal"
|
||||
@@ -159,6 +184,31 @@ interval:
|
||||
|
||||
# Dynamic controls exposed to Home Assistant for real-time camera tuning
|
||||
number:
|
||||
# The two that actually drive image brightness in manual mode.
|
||||
- platform: template
|
||||
name: "${friendly_name} Exposure"
|
||||
min_value: 0
|
||||
max_value: 1200
|
||||
step: 25
|
||||
initial_value: 1200
|
||||
optimistic: true
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) s->set_aec_value(s, (int)x);
|
||||
|
||||
- platform: template
|
||||
name: "${friendly_name} Gain"
|
||||
min_value: 0
|
||||
max_value: 30
|
||||
step: 1
|
||||
initial_value: 30
|
||||
optimistic: true
|
||||
set_action:
|
||||
- lambda: |-
|
||||
sensor_t *s = esp_camera_sensor_get();
|
||||
if (s) s->set_agc_gain(s, (int)x);
|
||||
|
||||
- platform: template
|
||||
name: "${friendly_name} Brightness"
|
||||
min_value: -2
|
||||
|
||||
@@ -1,38 +0,0 @@
|
||||
substitutions:
|
||||
name: family-room-remote
|
||||
friendly_name: "Family Room Remote"
|
||||
|
||||
packages:
|
||||
base: !include family-room-remote/family-room-remote.base.yaml
|
||||
ui: !include family-room-remote/family-room-remote.ui.yaml
|
||||
bindings: !include family-room-remote/family-room-remote.bindings.yaml
|
||||
|
||||
esphome:
|
||||
name: ${name}
|
||||
friendly_name: ${friendly_name}
|
||||
name_add_mac_suffix: false
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
variant: ESP32S3
|
||||
flash_size: 16MB
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
ap:
|
||||
ssid: "${friendly_name} Fallback"
|
||||
password: !secret fallback_password
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
logger:
|
||||
baud_rate: 0
|
||||
@@ -1,173 +0,0 @@
|
||||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
|
||||
i2c:
|
||||
id: i2c_main
|
||||
sda: GPIO19
|
||||
scl: GPIO20
|
||||
frequency: 400kHz
|
||||
scan: false
|
||||
|
||||
# --- Backlight control (GPIO2 direct) ---
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "LCD Backlight Raw"
|
||||
id: lcd_backlight_raw
|
||||
restore_mode: ALWAYS_ON
|
||||
pin:
|
||||
number: GPIO2
|
||||
mode:
|
||||
output: true
|
||||
|
||||
light:
|
||||
- platform: binary
|
||||
name: "Family Room Remote Backlight"
|
||||
output: lcd_backlight_out
|
||||
id: ha_remote_backlight
|
||||
restore_mode: ALWAYS_ON
|
||||
|
||||
output:
|
||||
- platform: template
|
||||
id: lcd_backlight_out
|
||||
type: binary
|
||||
write_action:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return state;
|
||||
then:
|
||||
- switch.turn_on: lcd_backlight_raw
|
||||
else:
|
||||
- switch.turn_off: lcd_backlight_raw
|
||||
|
||||
# --- Inactivity tracking (off after 2 minutes, wake on touch) ---
|
||||
globals:
|
||||
- id: last_activity_ms
|
||||
type: uint32_t
|
||||
restore_value: no
|
||||
initial_value: '0'
|
||||
|
||||
interval:
|
||||
- interval: 1s
|
||||
then:
|
||||
- lambda: |-
|
||||
if (id(last_activity_ms) == 0) id(last_activity_ms) = millis();
|
||||
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
const uint32_t idle_s = (millis() - id(last_activity_ms)) / 1000;
|
||||
return idle_s == 120;
|
||||
then:
|
||||
- light.turn_off: ha_remote_backlight
|
||||
|
||||
# --- Display (ST7262 parallel RGB, 800x480) ---
|
||||
display:
|
||||
- platform: rpi_dpi_rgb
|
||||
id: main_display
|
||||
dimensions:
|
||||
width: 800
|
||||
height: 480
|
||||
de_pin: GPIO40
|
||||
hsync_pin: GPIO39
|
||||
vsync_pin: GPIO41
|
||||
pclk_pin: GPIO42
|
||||
pclk_frequency: 16MHz
|
||||
color_order: BGR
|
||||
data_pins:
|
||||
red:
|
||||
- GPIO45
|
||||
- GPIO48
|
||||
- GPIO47
|
||||
- GPIO21
|
||||
- GPIO14
|
||||
green:
|
||||
- GPIO5
|
||||
- GPIO6
|
||||
- GPIO7
|
||||
- GPIO15
|
||||
- GPIO16
|
||||
- GPIO4
|
||||
blue:
|
||||
- GPIO8
|
||||
- GPIO3
|
||||
- GPIO46
|
||||
- GPIO9
|
||||
- GPIO1
|
||||
hsync_back_porch: 48
|
||||
hsync_front_porch: 40
|
||||
hsync_pulse_width: 48
|
||||
vsync_back_porch: 32
|
||||
vsync_front_porch: 13
|
||||
vsync_pulse_width: 3
|
||||
update_interval: never
|
||||
auto_clear_enabled: false
|
||||
|
||||
# --- Touch (GT911 capacitive) ---
|
||||
# NOTE: interrupt_pin GPIO18 requires soldering the R17 bridge on the board.
|
||||
# If not soldered, remove the interrupt_pin line (falls back to polling).
|
||||
touchscreen:
|
||||
platform: gt911
|
||||
id: touch_panel
|
||||
i2c_id: i2c_main
|
||||
reset_pin: GPIO38
|
||||
on_touch:
|
||||
then:
|
||||
- lambda: |-
|
||||
id(last_activity_ms) = millis();
|
||||
- light.turn_on: ha_remote_backlight
|
||||
|
||||
# --- MDI Icon Font ---
|
||||
font:
|
||||
- file: "https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/fonts/materialdesignicons-webfont.ttf"
|
||||
id: mdi_icons
|
||||
size: 24
|
||||
bpp: 4
|
||||
glyphs:
|
||||
- "\U000F0335" # mdi:lightbulb
|
||||
- "\U000F0425" # mdi:power
|
||||
- "\U000F0079" # mdi:battery
|
||||
- "\U000F12A1" # mdi:battery-low
|
||||
- "\U000F12A2" # mdi:battery-medium
|
||||
- "\U000F12A3" # mdi:battery-high
|
||||
- "\U000F12A4" # mdi:battery-charging-low
|
||||
- "\U000F12A5" # mdi:battery-charging-medium
|
||||
- "\U000F12A6" # mdi:battery-charging-high
|
||||
- "\U000F0091" # mdi:battery-unknown
|
||||
- "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||
|
||||
# --- Battery fuel gauge (MAX17048 via I2C) ---
|
||||
# DISABLED for testing - suspected display interference
|
||||
# sensor:
|
||||
# - platform: max17043
|
||||
# id: max17048_battery
|
||||
# i2c_id: i2c_main
|
||||
# update_interval: 120s
|
||||
# battery_voltage:
|
||||
# name: "Remote Battery Voltage"
|
||||
# id: remote_battery_voltage
|
||||
# entity_category: diagnostic
|
||||
# device_class: voltage
|
||||
# state_class: measurement
|
||||
# accuracy_decimals: 3
|
||||
# battery_level:
|
||||
# name: "Remote Battery Level"
|
||||
# id: remote_battery_level
|
||||
# entity_category: diagnostic
|
||||
# device_class: battery
|
||||
# state_class: measurement
|
||||
# accuracy_decimals: 0
|
||||
# on_value:
|
||||
# then:
|
||||
# - lambda: |-
|
||||
# ESP_LOGI("battery", "Level: %.0f%%", x);
|
||||
# // Update the LVGL battery icon based on level
|
||||
# lv_obj_t *lbl = id(battery_icon);
|
||||
# if (x < 10)
|
||||
# lv_label_set_text(lbl, "\U000F10CD"); // battery-alert-variant-outline
|
||||
# else if (x < 30)
|
||||
# lv_label_set_text(lbl, "\U000F12A1"); // battery-low
|
||||
# else if (x < 70)
|
||||
# lv_label_set_text(lbl, "\U000F12A2"); // battery-medium
|
||||
# else
|
||||
# lv_label_set_text(lbl, "\U000F12A3"); // battery-high
|
||||
@@ -1,59 +0,0 @@
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "Family Room Remote IP"
|
||||
id: ip_addr
|
||||
|
||||
- platform: homeassistant
|
||||
id: ts_family_room_tv_stand
|
||||
entity_id: light.family_room_tv_stand
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_family_room_tv_stand
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_family_room_tv_stand);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
|
||||
- platform: homeassistant
|
||||
id: ts_small_family_room_lamp
|
||||
entity_id: light.small_family_room_lamp
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_small_family_room_lamp
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_small_family_room_lamp);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
|
||||
- platform: homeassistant
|
||||
id: ts_big_family_room_lamp
|
||||
entity_id: switch.big_family_room_lamp
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_big_family_room_lamp
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_big_family_room_lamp);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
@@ -1,231 +0,0 @@
|
||||
lvgl:
|
||||
displays:
|
||||
- main_display
|
||||
touchscreens:
|
||||
- touch_panel
|
||||
disp_bg_color: 0x141218
|
||||
style_definitions:
|
||||
# ── Top App Bar ──
|
||||
- id: md3_header
|
||||
bg_color: 0x1D1B20
|
||||
bg_opa: 100%
|
||||
border_width: 0
|
||||
radius: 0
|
||||
pad_all: 0
|
||||
shadow_width: 0
|
||||
# ── Status Chip ──
|
||||
- id: md3_chip
|
||||
bg_color: 0x2B2930
|
||||
bg_opa: 100%
|
||||
border_color: 0x49454F
|
||||
border_width: 1
|
||||
border_opa: 100%
|
||||
radius: 8
|
||||
pad_all: 0
|
||||
# ── Text Styles ──
|
||||
- id: md3_title
|
||||
text_color: 0xE3E2E6
|
||||
text_opa: 100%
|
||||
- id: md3_meta
|
||||
text_color: 0xC4C6CF
|
||||
text_opa: 100%
|
||||
- id: md3_icon
|
||||
text_color: 0xC4C6CF
|
||||
text_opa: 100%
|
||||
# ── Card Variants ──
|
||||
- id: md3_card_blue
|
||||
bg_color: 0x2B2930
|
||||
bg_opa: 100%
|
||||
border_color: 0x49454F
|
||||
border_width: 1
|
||||
border_opa: 100%
|
||||
radius: 16
|
||||
shadow_width: 0
|
||||
- id: md3_card_pink
|
||||
bg_color: 0x2B2930
|
||||
bg_opa: 100%
|
||||
border_color: 0x49454F
|
||||
border_width: 1
|
||||
border_opa: 100%
|
||||
radius: 16
|
||||
shadow_width: 0
|
||||
- id: md3_card_green
|
||||
bg_color: 0x2B2930
|
||||
bg_opa: 100%
|
||||
border_color: 0x49454F
|
||||
border_width: 1
|
||||
border_opa: 100%
|
||||
radius: 16
|
||||
shadow_width: 0
|
||||
theme:
|
||||
label:
|
||||
text_color: 0xE3E2E6
|
||||
button:
|
||||
text_color: 0xE3E2E6
|
||||
pages:
|
||||
- id: home
|
||||
widgets:
|
||||
# ── Header ──
|
||||
- obj:
|
||||
x: 0
|
||||
y: 0
|
||||
width: 800
|
||||
height: 64
|
||||
styles: md3_header
|
||||
widgets:
|
||||
- label:
|
||||
x: 24
|
||||
y: 20
|
||||
text_color: 0xE3E2E6
|
||||
text: "Family Room"
|
||||
|
||||
# ── TV Stand ──
|
||||
- button:
|
||||
id: btn_family_room_tv_stand
|
||||
x: 24
|
||||
y: 80
|
||||
width: 234
|
||||
height: 300
|
||||
checkable: true
|
||||
styles: md3_card_blue
|
||||
checked:
|
||||
bg_color: 0xA8C7FA
|
||||
bg_opa: 100%
|
||||
border_color: 0xA8C7FA
|
||||
border_opa: 100%
|
||||
text_color: 0x001A33
|
||||
on_click:
|
||||
- homeassistant.service:
|
||||
service: light.toggle
|
||||
data:
|
||||
entity_id: light.family_room_tv_stand
|
||||
widgets:
|
||||
- label:
|
||||
x: 194
|
||||
y: 16
|
||||
styles: md3_icon
|
||||
text_font: mdi_icons
|
||||
text: "\U000F0335" # mdi:lightbulb
|
||||
- label:
|
||||
x: 18
|
||||
y: 16
|
||||
styles: md3_meta
|
||||
text: "LIGHT"
|
||||
- label:
|
||||
x: 18
|
||||
y: 56
|
||||
styles: md3_title
|
||||
text: "TV Stand"
|
||||
- label:
|
||||
x: 18
|
||||
y: 90
|
||||
styles: md3_meta
|
||||
text: "Tap to toggle"
|
||||
|
||||
# ── Small Lamp ──
|
||||
- button:
|
||||
id: btn_small_family_room_lamp
|
||||
x: 282
|
||||
y: 80
|
||||
width: 234
|
||||
height: 300
|
||||
checkable: true
|
||||
styles: md3_card_pink
|
||||
checked:
|
||||
bg_color: 0xFFD7F1
|
||||
bg_opa: 100%
|
||||
border_color: 0xFFD7F1
|
||||
border_opa: 100%
|
||||
text_color: 0x22001F
|
||||
on_click:
|
||||
- homeassistant.service:
|
||||
service: light.toggle
|
||||
data:
|
||||
entity_id: light.small_family_room_lamp
|
||||
widgets:
|
||||
- label:
|
||||
x: 194
|
||||
y: 16
|
||||
styles: md3_icon
|
||||
text_font: mdi_icons
|
||||
text: "\U000F0335" # mdi:lightbulb
|
||||
- label:
|
||||
x: 18
|
||||
y: 16
|
||||
styles: md3_meta
|
||||
text: "LIGHT"
|
||||
- label:
|
||||
x: 18
|
||||
y: 56
|
||||
styles: md3_title
|
||||
text: "Small Lamp"
|
||||
- label:
|
||||
x: 18
|
||||
y: 90
|
||||
styles: md3_meta
|
||||
text: "Tap to toggle"
|
||||
|
||||
# ── Standing Lamp ──
|
||||
- button:
|
||||
id: btn_big_family_room_lamp
|
||||
x: 540
|
||||
y: 80
|
||||
width: 236
|
||||
height: 300
|
||||
checkable: true
|
||||
styles: md3_card_green
|
||||
checked:
|
||||
bg_color: 0xB5CCBA
|
||||
bg_opa: 100%
|
||||
border_color: 0xB5CCBA
|
||||
border_opa: 100%
|
||||
text_color: 0x001F15
|
||||
on_click:
|
||||
- homeassistant.service:
|
||||
service: light.toggle
|
||||
data:
|
||||
entity_id: light.living_room_lamp_1
|
||||
widgets:
|
||||
- label:
|
||||
x: 196
|
||||
y: 16
|
||||
styles: md3_icon
|
||||
text_font: mdi_icons
|
||||
text: "\U000F0335" # mdi:lightbulb
|
||||
- label:
|
||||
x: 18
|
||||
y: 16
|
||||
styles: md3_meta
|
||||
text: "LIGHT"
|
||||
- label:
|
||||
x: 18
|
||||
y: 56
|
||||
styles: md3_title
|
||||
text: "Standing Lamp"
|
||||
- label:
|
||||
x: 18
|
||||
y: 90
|
||||
styles: md3_meta
|
||||
text: "Tap to toggle"
|
||||
|
||||
# ── Status Bar ──
|
||||
- obj:
|
||||
x: 0
|
||||
y: 400
|
||||
width: 800
|
||||
height: 80
|
||||
styles: md3_header
|
||||
widgets:
|
||||
- obj:
|
||||
x: 8
|
||||
y: 20
|
||||
width: 84
|
||||
height: 40
|
||||
styles: md3_chip
|
||||
widgets:
|
||||
- label:
|
||||
id: battery_icon
|
||||
align: center
|
||||
text_font: mdi_icons
|
||||
text_color: 0xC4C6CF
|
||||
text: "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||
@@ -1 +0,0 @@
|
||||
CODEOWNERS = ["@blacknell"]
|
||||
@@ -1,20 +0,0 @@
|
||||
|
||||
#pragma once
|
||||
#include "esphome/core/automation.h"
|
||||
#include "max17043.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max17043 {
|
||||
|
||||
template<typename... Ts> class SleepAction : public Action<Ts...> {
|
||||
public:
|
||||
explicit SleepAction(MAX17043Component *max17043) : max17043_(max17043) {}
|
||||
|
||||
void play(Ts... x) override { this->max17043_->sleep_mode(); }
|
||||
|
||||
protected:
|
||||
MAX17043Component *max17043_;
|
||||
};
|
||||
|
||||
} // namespace max17043
|
||||
} // namespace esphome
|
||||
@@ -1,72 +0,0 @@
|
||||
#include "max17043.h"
|
||||
#include "esphome/core/log.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max17043 {
|
||||
|
||||
// MAX174043 is a 1-Cell Fuel Gauge with ModelGauge and Low-Battery Alert
|
||||
// Consult the datasheet at https://www.analog.com/en/products/max17043.html
|
||||
|
||||
static const char *const TAG = "max17043";
|
||||
|
||||
static const uint8_t MAX17043_VCELL = 0x02;
|
||||
static const uint8_t MAX17043_SOC = 0x04;
|
||||
static const uint8_t MAX17043_CONFIG = 0x0c;
|
||||
|
||||
static const uint16_t MAX17043_CONFIG_POWER_UP_DEFAULT = 0x971C;
|
||||
static const uint16_t MAX17043_CONFIG_SAFE_MASK = 0xFF1F; // mask out sleep bit (7), unused bit (6) and alert bit (4)
|
||||
static const uint16_t MAX17043_CONFIG_SLEEP_MASK = 0x0080;
|
||||
|
||||
void MAX17043Component::update() {
|
||||
uint16_t raw_voltage, raw_percent;
|
||||
|
||||
if (this->voltage_sensor_ != nullptr) {
|
||||
if (!this->read_byte_16(MAX17043_VCELL, &raw_voltage)) {
|
||||
this->status_set_warning("Unable to read MAX17043_VCELL");
|
||||
} else {
|
||||
float voltage = (1.25 * (float) (raw_voltage >> 4)) / 1000.0;
|
||||
this->voltage_sensor_->publish_state(voltage);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
if (this->battery_remaining_sensor_ != nullptr) {
|
||||
if (!this->read_byte_16(MAX17043_SOC, &raw_percent)) {
|
||||
this->status_set_warning("Unable to read MAX17043_SOC");
|
||||
} else {
|
||||
float percent = (float) ((raw_percent >> 8) + 0.003906f * (raw_percent & 0x00ff));
|
||||
this->battery_remaining_sensor_->publish_state(percent);
|
||||
this->status_clear_warning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void MAX17043Component::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up MAX17043...");
|
||||
// Compatible mode for MAX17048/variant boards:
|
||||
// avoid setup-time register writes on the shared touch I2C bus.
|
||||
}
|
||||
|
||||
void MAX17043Component::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "MAX17043:");
|
||||
LOG_I2C_DEVICE(this);
|
||||
if (this->is_failed()) {
|
||||
ESP_LOGE(TAG, "Communication with MAX17043 failed");
|
||||
}
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
LOG_SENSOR(" ", "Battery Voltage", this->voltage_sensor_);
|
||||
LOG_SENSOR(" ", "Battery Level", this->battery_remaining_sensor_);
|
||||
}
|
||||
|
||||
float MAX17043Component::get_setup_priority() const { return setup_priority::DATA; }
|
||||
|
||||
void MAX17043Component::sleep_mode() {
|
||||
if (!this->is_failed()) {
|
||||
if (!this->write_byte_16(MAX17043_CONFIG, MAX17043_CONFIG_POWER_UP_DEFAULT | MAX17043_CONFIG_SLEEP_MASK)) {
|
||||
ESP_LOGW(TAG, "Unable to write the sleep bit to config register");
|
||||
this->status_set_warning();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace max17043
|
||||
} // namespace esphome
|
||||
@@ -1,29 +0,0 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#include "esphome/components/i2c/i2c.h"
|
||||
|
||||
namespace esphome {
|
||||
namespace max17043 {
|
||||
|
||||
class MAX17043Component : public PollingComponent, public i2c::I2CDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override;
|
||||
void update() override;
|
||||
void sleep_mode();
|
||||
|
||||
void set_voltage_sensor(sensor::Sensor *voltage_sensor) { voltage_sensor_ = voltage_sensor; }
|
||||
void set_battery_remaining_sensor(sensor::Sensor *battery_remaining_sensor) {
|
||||
battery_remaining_sensor_ = battery_remaining_sensor;
|
||||
}
|
||||
|
||||
protected:
|
||||
sensor::Sensor *voltage_sensor_{nullptr};
|
||||
sensor::Sensor *battery_remaining_sensor_{nullptr};
|
||||
};
|
||||
|
||||
} // namespace max17043
|
||||
} // namespace esphome
|
||||
@@ -1,77 +0,0 @@
|
||||
from esphome import automation
|
||||
from esphome.automation import maybe_simple_id
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import i2c, sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_BATTERY_LEVEL,
|
||||
CONF_BATTERY_VOLTAGE,
|
||||
CONF_ID,
|
||||
DEVICE_CLASS_BATTERY,
|
||||
DEVICE_CLASS_VOLTAGE,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_PERCENT,
|
||||
UNIT_VOLT,
|
||||
)
|
||||
|
||||
DEPENDENCIES = ["i2c"]
|
||||
|
||||
max17043_ns = cg.esphome_ns.namespace("max17043")
|
||||
MAX17043Component = max17043_ns.class_(
|
||||
"MAX17043Component", cg.PollingComponent, i2c.I2CDevice
|
||||
)
|
||||
|
||||
# Actions
|
||||
SleepAction = max17043_ns.class_("SleepAction", automation.Action)
|
||||
|
||||
CONFIG_SCHEMA = (
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(MAX17043Component),
|
||||
cv.Optional(CONF_BATTERY_VOLTAGE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_VOLT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_VOLTAGE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
cv.Optional(CONF_BATTERY_LEVEL): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_PERCENT,
|
||||
accuracy_decimals=3,
|
||||
device_class=DEVICE_CLASS_BATTERY,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
),
|
||||
}
|
||||
)
|
||||
.extend(cv.polling_component_schema("60s"))
|
||||
.extend(i2c.i2c_device_schema(0x36))
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
var = cg.new_Pvariable(config[CONF_ID])
|
||||
await cg.register_component(var, config)
|
||||
await i2c.register_i2c_device(var, config)
|
||||
|
||||
if voltage_config := config.get(CONF_BATTERY_VOLTAGE):
|
||||
sens = await sensor.new_sensor(voltage_config)
|
||||
cg.add(var.set_voltage_sensor(sens))
|
||||
|
||||
if CONF_BATTERY_LEVEL in config:
|
||||
sens = await sensor.new_sensor(config[CONF_BATTERY_LEVEL])
|
||||
cg.add(var.set_battery_remaining_sensor(sens))
|
||||
|
||||
|
||||
MAX17043_ACTION_SCHEMA = maybe_simple_id(
|
||||
{
|
||||
cv.Required(CONF_ID): cv.use_id(MAX17043Component),
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
@automation.register_action("max17043.sleep_mode", SleepAction, MAX17043_ACTION_SCHEMA)
|
||||
async def max17043_sleep_mode_to_code(config, action_id, template_arg, args):
|
||||
paren = await cg.get_variable(config[CONF_ID])
|
||||
return cg.new_Pvariable(action_id, template_arg, paren)
|
||||
@@ -1,176 +0,0 @@
|
||||
psram:
|
||||
mode: octal
|
||||
speed: 80MHz
|
||||
|
||||
i2c:
|
||||
id: i2c_main
|
||||
sda: 8
|
||||
scl: 9
|
||||
frequency: 400kHz
|
||||
scan: false
|
||||
|
||||
# CH422G I/O expander (Waveshare uses it for LCD reset/backlight/touch reset)
|
||||
ch422g:
|
||||
- id: ch422g_hub
|
||||
i2c_id: i2c_main
|
||||
|
||||
# --- Backlight control (CH422G IO2 is common for Waveshare LCD BL) ---
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "LCD Backlight Raw"
|
||||
id: lcd_backlight_raw
|
||||
restore_mode: ALWAYS_ON
|
||||
pin:
|
||||
ch422g: ch422g_hub
|
||||
number: 2
|
||||
mode:
|
||||
output: true
|
||||
|
||||
# A nicer HA-exposed control (so you can also automate it from HA)
|
||||
light:
|
||||
- platform: binary
|
||||
name: "HA Remote Backlight"
|
||||
output: lcd_backlight_out
|
||||
id: ha_remote_backlight
|
||||
|
||||
output:
|
||||
- platform: template
|
||||
id: lcd_backlight_out
|
||||
type: binary
|
||||
write_action:
|
||||
- if:
|
||||
condition:
|
||||
lambda: return state;
|
||||
then:
|
||||
- switch.turn_on: lcd_backlight_raw
|
||||
else:
|
||||
- switch.turn_off: lcd_backlight_raw
|
||||
|
||||
# --- Inactivity tracking (dim/off + wake on touch) ---
|
||||
globals:
|
||||
- id: last_activity_ms
|
||||
type: uint32_t
|
||||
restore_value: no
|
||||
initial_value: '0'
|
||||
|
||||
interval:
|
||||
- interval: 1s
|
||||
then:
|
||||
- lambda: |-
|
||||
if (id(last_activity_ms) == 0) id(last_activity_ms) = millis();
|
||||
|
||||
# Turn off backlight ONCE at 2 minutes idle.
|
||||
- if:
|
||||
condition:
|
||||
lambda: |-
|
||||
const uint32_t idle_s = (millis() - id(last_activity_ms)) / 1000;
|
||||
return idle_s == 120;
|
||||
then:
|
||||
- light.turn_off: ha_remote_backlight
|
||||
|
||||
# --- Display ---
|
||||
display:
|
||||
- platform: mipi_rgb
|
||||
model: ESP32-S3-TOUCH-LCD-7-800X480
|
||||
id: main_display
|
||||
update_interval: never
|
||||
auto_clear_enabled: false
|
||||
reset_pin:
|
||||
ch422g: ch422g_hub
|
||||
number: 3
|
||||
mode:
|
||||
output: true
|
||||
|
||||
# --- Touch ---
|
||||
touchscreen:
|
||||
platform: gt911
|
||||
id: touch_panel
|
||||
i2c_id: i2c_main
|
||||
interrupt_pin: 4
|
||||
reset_pin:
|
||||
ch422g: ch422g_hub
|
||||
number: 1
|
||||
mode:
|
||||
output: true
|
||||
on_touch:
|
||||
then:
|
||||
- lambda: |-
|
||||
id(last_activity_ms) = millis();
|
||||
- light.turn_on: ha_remote_backlight
|
||||
|
||||
# --- MDI Icon Font ---
|
||||
font:
|
||||
- file: "https://raw.githubusercontent.com/Templarian/MaterialDesign-Webfont/master/fonts/materialdesignicons-webfont.ttf"
|
||||
id: mdi_icons
|
||||
size: 24
|
||||
bpp: 4
|
||||
glyphs:
|
||||
# Tile icons
|
||||
- "\U000F0335" # mdi:lightbulb
|
||||
- "\U000F0425" # mdi:power
|
||||
- "\U000F0426" # mdi:power-plug
|
||||
- "\U000F07E9" # mdi:power-socket-us
|
||||
- "\U000F1A26" # mdi:toggle-switch-variant-off
|
||||
# Battery status icons
|
||||
- "\U000F0079" # mdi:battery
|
||||
- "\U000F12A1" # mdi:battery-low
|
||||
- "\U000F12A2" # mdi:battery-medium
|
||||
- "\U000F12A3" # mdi:battery-high
|
||||
- "\U000F12A4" # mdi:battery-charging-low
|
||||
- "\U000F12A5" # mdi:battery-charging-medium
|
||||
- "\U000F12A6" # mdi:battery-charging-high
|
||||
- "\U000F0091" # mdi:battery-unknown
|
||||
- "\U000F10CD" # mdi:battery-alert-variant-outline
|
||||
# Navigation bar icons
|
||||
- "\U000F04B9" # mdi:sofa
|
||||
- "\U000F06B5" # mdi:lamp
|
||||
- "\U000F04DE" # mdi:stove
|
||||
- "\U000F12BD" # mdi:stairs-up
|
||||
- "\U000F1239" # mdi:desk
|
||||
- "\U000F06D9" # mdi:garage
|
||||
- "\U000F0531" # mdi:tree
|
||||
|
||||
# --- LVGL UI ---
|
||||
# Note: On ESP32-S3-Touch-LCD-7, GPIO14 is used by the RGB display bus,
|
||||
# so it cannot be reused as ADC for battery telemetry in this display mode.
|
||||
|
||||
# --- Battery fuel gauge (Adafruit MAX17048 via compatible driver) ---
|
||||
sensor:
|
||||
- platform: max17043
|
||||
id: max17048_battery
|
||||
i2c_id: i2c_main
|
||||
update_interval: 120s
|
||||
battery_voltage:
|
||||
name: "Remote Battery Voltage"
|
||||
id: remote_battery_voltage
|
||||
entity_category: diagnostic
|
||||
device_class: voltage
|
||||
state_class: measurement
|
||||
accuracy_decimals: 3
|
||||
on_value:
|
||||
then:
|
||||
- lambda: |-
|
||||
ESP_LOGI("battery", "Voltage: %.3f V", x);
|
||||
battery_level:
|
||||
name: "Remote Battery Level"
|
||||
id: remote_battery_level
|
||||
entity_category: diagnostic
|
||||
device_class: battery
|
||||
state_class: measurement
|
||||
accuracy_decimals: 0
|
||||
on_value:
|
||||
then:
|
||||
- lambda: |-
|
||||
const float pct = x;
|
||||
ESP_LOGI("battery", "Level: %.0f%%", pct);
|
||||
# - platform: custom
|
||||
# lambda: |-
|
||||
# auto max17048_sensor = new MAX17048Sensor();
|
||||
# App.register_component(max17048_sensor);
|
||||
# return {max17048_sensor->voltage_sensor, max17048_sensor->percentage_sensor};
|
||||
# sensors:
|
||||
# - name: "Voltage"
|
||||
# unit_of_measurement: V
|
||||
# accuracy_decimals: 2
|
||||
# - name: "Percentage"
|
||||
# unit_of_measurement: '%'
|
||||
@@ -1,383 +0,0 @@
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "HA Remote IP"
|
||||
id: ip_addr
|
||||
- platform: homeassistant
|
||||
id: ts_family_room_tv_stand
|
||||
entity_id: light.family_room_tv_stand
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_family_room_tv_stand
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_family_room_tv_stand);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_small_family_room_lamp
|
||||
entity_id: light.small_family_room_lamp
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_small_family_room_lamp
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_small_family_room_lamp);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_living_room_lamp_1
|
||||
entity_id: light.living_room_lamp_1
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_family_room_standing_lamp
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x3D0002) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_family_room_standing_lamp);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_living_room_light_2
|
||||
entity_id: light.living_room_light_2
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_living_room_main_light
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_living_room_main_light);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_patio_light_1
|
||||
entity_id: light.patio_light_1
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_living_room_patio_light
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lvgl.widget.update:
|
||||
id: btn_outside_patio_light
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t off_color = lv_color_hex(0xE3E2E6);
|
||||
auto *btn1 = id(btn_living_room_patio_light);
|
||||
auto *btn2 = id(btn_outside_patio_light);
|
||||
if (x == "on") {
|
||||
lv_obj_set_style_text_color(btn1, lv_color_hex(0x1A0C00), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn1); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn1, i), lv_color_hex(0x1A0C00), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
lv_obj_set_style_text_color(btn2, lv_color_hex(0x001A33), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn2); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn2, i), lv_color_hex(0x001A33), static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
} else {
|
||||
lv_obj_set_style_text_color(btn1, off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn1); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn1, i), off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
lv_obj_set_style_text_color(btn2, off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn2); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn2, i), off_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_kitchen_sink_light
|
||||
entity_id: light.kitchen_sink_light
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_kitchen_sink_light
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_kitchen_sink_light);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_office_lamp_2
|
||||
entity_id: light.office_lamp_2
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_office_lamp
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_office_lamp);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_office_led_strip
|
||||
entity_id: light.led_strip_controller_led_strip_controller
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_office_led_strip
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_office_led_strip);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_garage_cam_light
|
||||
entity_id: light.esp32_saturn4_cam_esp32_saturn4_cam_light
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_garage_cam_light
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_garage_cam_light);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_office_echo_plug
|
||||
entity_id: switch.office_echo_plug
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_office_echo_plug
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_office_echo_plug);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_office_end_table_lamp_outlet
|
||||
entity_id: switch.office_end_table_lamp_socket_1
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_office_end_table_lamp_outlet
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_office_end_table_lamp_outlet);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_office_wax_warmer_outlet
|
||||
entity_id: switch.office_wax_warmer_socket_1
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_office_wax_warmer_outlet
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_office_wax_warmer_outlet);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_outside_lamppost_outlet_1
|
||||
entity_id: switch.lamppost_outlets_socket_1
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_outside_lamppost_outlet_1
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_outside_lamppost_outlet_1);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_outside_lamppost_outlet_2
|
||||
entity_id: switch.lamppost_outlets_socket_2
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_outside_lamppost_outlet_2
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_outside_lamppost_outlet_2);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_outside_porch_decor_outlet
|
||||
entity_id: switch.washing_machine_socket_1
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_outside_porch_decor_outlet
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_outside_porch_decor_outlet);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_living_room_garland_switch
|
||||
entity_id: switch.big_family_room_lamp
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_living_room_garland_switch
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001F15) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_living_room_garland_switch);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_living_room_calendar_switch
|
||||
entity_id: switch.digital_calendar
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_living_room_calendar_switch
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1E0F3F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_living_room_calendar_switch);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_upstairs_airquality_oled
|
||||
entity_id: switch.airqualitysensor_3_oled_power
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_upstairs_airquality_oled
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x001A33) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_upstairs_airquality_oled);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_upstairs_camera_motion
|
||||
entity_id: switch.upstairs_camera_motion_detection
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_upstairs_camera_motion
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_upstairs_camera_motion);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_garage_fume_exhaust_fan
|
||||
entity_id: switch.fume_exhaust_fan
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_garage_fume_exhaust_fan
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x22001F) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_garage_fume_exhaust_fan);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
- platform: homeassistant
|
||||
id: ts_garage_resin_printer_heater
|
||||
entity_id: switch.resin_printer_heater
|
||||
internal: true
|
||||
on_value:
|
||||
then:
|
||||
- lvgl.widget.update:
|
||||
id: btn_garage_resin_printer_heater
|
||||
state:
|
||||
checked: !lambda return x == "on";
|
||||
- lambda: |-
|
||||
const lv_color_t text_color = (x == "on") ? lv_color_hex(0x1A0C00) : lv_color_hex(0xE3E2E6);
|
||||
auto *btn = id(btn_garage_resin_printer_heater);
|
||||
lv_obj_set_style_text_color(btn, text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
for (uint32_t i = 0; i < lv_obj_get_child_cnt(btn); i++) {
|
||||
lv_obj_set_style_text_color(lv_obj_get_child(btn, i), text_color, static_cast<lv_style_selector_t>(LV_PART_MAIN | LV_STATE_DEFAULT));
|
||||
}
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,49 +0,0 @@
|
||||
# ===== I2S Audio =====
|
||||
i2s_audio:
|
||||
- id: i2s_in
|
||||
i2s_lrclk_pin: ${i2s_in_lrclk_pin}
|
||||
i2s_bclk_pin: ${i2s_in_bclk_pin}
|
||||
- id: i2s_out
|
||||
i2s_lrclk_pin: ${i2s_out_lrclk_pin}
|
||||
i2s_bclk_pin: ${i2s_out_bclk_pin}
|
||||
|
||||
microphone:
|
||||
- platform: i2s_audio
|
||||
id: mic
|
||||
i2s_audio_id: i2s_in
|
||||
i2s_din_pin: ${mic_din_pin}
|
||||
adc_type: external
|
||||
pdm: false
|
||||
bits_per_sample: 32bit
|
||||
channel: left
|
||||
|
||||
speaker:
|
||||
- platform: i2s_audio
|
||||
id: spk
|
||||
i2s_audio_id: i2s_out
|
||||
i2s_dout_pin: ${speaker_dout_pin}
|
||||
dac_type: external
|
||||
bits_per_sample: 32bit
|
||||
channel: mono
|
||||
sample_rate: 16000
|
||||
# Voice Assistant needs the speaker to fully stop after playback so it can
|
||||
# leave RESPONSE_FINISHED and return to IDLE for the next wake word.
|
||||
timeout: 500ms
|
||||
|
||||
rtttl:
|
||||
id: volume_beep
|
||||
speaker: spk
|
||||
|
||||
# ===== Wake Word =====
|
||||
micro_wake_word:
|
||||
id: mww
|
||||
microphone: mic
|
||||
models:
|
||||
- model: okay_nabu
|
||||
id: wake_word_model
|
||||
on_wake_word_detected:
|
||||
- voice_assistant.start:
|
||||
wake_word: !lambda return wake_word;
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Listening Pulse"
|
||||
@@ -1,125 +0,0 @@
|
||||
packages:
|
||||
jarvis-satellite-led: !include jarvis-satellite-led.yaml
|
||||
jarvis-satellite-voice: !include jarvis-satellite-voice.yaml
|
||||
jarvis-satellite-audio: !include jarvis-satellite-audio.yaml
|
||||
jarvis-satellite-ui: !include jarvis-satellite-ui.yaml
|
||||
|
||||
substitutions:
|
||||
# Flash & PSRAM
|
||||
flash_size: "8MB"
|
||||
psram_mode: "quad"
|
||||
psram_speed: "80MHz"
|
||||
# CPU Frequency
|
||||
cpu_frequency: "240MHz"
|
||||
|
||||
# I2S Microphone pins
|
||||
i2s_in_lrclk_pin: GPIO6
|
||||
i2s_in_bclk_pin: GPIO7
|
||||
mic_din_pin: GPIO4
|
||||
|
||||
# I2S Speaker pins
|
||||
i2s_out_lrclk_pin: GPIO45
|
||||
i2s_out_bclk_pin: GPIO46
|
||||
speaker_dout_pin: GPIO8
|
||||
|
||||
# LED bar
|
||||
led_pin: GPIO16
|
||||
led_num_leds: "8"
|
||||
|
||||
# Microphone sensitivity (tuned for INMP441)
|
||||
mic_noise_suppression_level: "2"
|
||||
mic_auto_gain: "10dBFS"
|
||||
mic_volume_multiplier: "4.0"
|
||||
|
||||
# Button pins
|
||||
btn_vol_up_pin: GPIO1
|
||||
btn_vol_down_pin: GPIO2
|
||||
btn_mute_pin: GPIO3
|
||||
btn_wake_pin: GPIO10
|
||||
|
||||
# Change this to true in case you have a hidden SSID at home.
|
||||
hidden_ssid: "true"
|
||||
|
||||
|
||||
globals:
|
||||
- id: volume_level
|
||||
type: float
|
||||
restore_value: true
|
||||
initial_value: '0.8'
|
||||
|
||||
esphome:
|
||||
platformio_options:
|
||||
board_build.flash_mode: dio
|
||||
on_boot:
|
||||
- priority: -100
|
||||
then:
|
||||
- lambda: id(spk).set_volume(id(volume_level));
|
||||
|
||||
esp32:
|
||||
board: esp32-s3-devkitc-1
|
||||
variant: esp32s3
|
||||
cpu_frequency: ${cpu_frequency}
|
||||
flash_size: ${flash_size}
|
||||
framework:
|
||||
type: esp-idf
|
||||
version: recommended
|
||||
sdkconfig_options:
|
||||
CONFIG_ESP32S3_DEFAULT_CPU_FREQ_240: "y"
|
||||
CONFIG_ESP32S3_DATA_CACHE_64KB: "y"
|
||||
CONFIG_ESP32S3_DATA_CACHE_LINE_64B: "y"
|
||||
CONFIG_ESP32S3_INSTRUCTION_CACHE_32KB: "y"
|
||||
|
||||
# Moves instructions and read only data from flash into PSRAM on boot.
|
||||
# Both enabled allows instructions to execute while a flash operation is in progress without needing to be placed in IRAM.
|
||||
# Considerably speeds up mWW at the cost of using more PSRAM.
|
||||
CONFIG_SPIRAM_RODATA: "y"
|
||||
CONFIG_SPIRAM_FETCH_INSTRUCTIONS: "y"
|
||||
|
||||
CONFIG_BT_ALLOCATION_FROM_SPIRAM_FIRST: "y"
|
||||
CONFIG_BT_BLE_DYNAMIC_ENV_MEMORY: "y"
|
||||
|
||||
CONFIG_MBEDTLS_EXTERNAL_MEM_ALLOC: "y"
|
||||
CONFIG_MBEDTLS_SSL_PROTO_TLS1_3: "y"
|
||||
|
||||
wifi:
|
||||
id: wifi_id
|
||||
fast_connect: ${hidden_ssid}
|
||||
|
||||
network:
|
||||
enable_ipv6: true
|
||||
|
||||
http_request:
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
psram:
|
||||
mode: ${psram_mode}
|
||||
speed: ${psram_speed}
|
||||
|
||||
captive_portal:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
on_client_connected:
|
||||
- delay: 50ms
|
||||
- if:
|
||||
condition:
|
||||
not:
|
||||
lambda: 'return id(mww).is_running();'
|
||||
then:
|
||||
- micro_wake_word.start:
|
||||
on_client_disconnected:
|
||||
- micro_wake_word.stop:
|
||||
- voice_assistant.stop:
|
||||
|
||||
logger:
|
||||
level: DEBUG
|
||||
logs:
|
||||
micro_wake_word: DEBUG
|
||||
voice_assistant: DEBUG
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
@@ -1,111 +0,0 @@
|
||||
# ===== Physical Buttons =====
|
||||
# Note: esp32_touch is not supported on ESP32-S3 with ESP-IDF.
|
||||
# Wire physical momentary buttons to these pins (active LOW with internal pullup).
|
||||
binary_sensor:
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: ${btn_vol_up_pin}
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
name: "Volume Up"
|
||||
on_press:
|
||||
- lambda: |-
|
||||
float vol = min(1.0f, id(volume_level) + 0.1f);
|
||||
id(volume_level) = vol;
|
||||
id(spk).set_volume(vol);
|
||||
- rtttl.play:
|
||||
id: volume_beep
|
||||
rtttl: "beep:d=32,o=6,b=180:c"
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
brightness: 100%
|
||||
effect: "none"
|
||||
- delay: 150ms
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Idle Breathe"
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: ${btn_vol_down_pin}
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
name: "Volume Down"
|
||||
on_press:
|
||||
- lambda: |-
|
||||
float vol = max(0.0f, id(volume_level) - 0.1f);
|
||||
id(volume_level) = vol;
|
||||
id(spk).set_volume(vol);
|
||||
- rtttl.play:
|
||||
id: volume_beep
|
||||
rtttl: "beep:d=32,o=5,b=180:c"
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
brightness: 30%
|
||||
effect: "none"
|
||||
- delay: 150ms
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Idle Breathe"
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: ${btn_mute_pin}
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
name: "Mic Mute"
|
||||
on_press:
|
||||
- switch.toggle: mic_mute
|
||||
|
||||
- platform: gpio
|
||||
pin:
|
||||
number: ${btn_wake_pin}
|
||||
mode: INPUT_PULLUP
|
||||
inverted: true
|
||||
name: "Wake"
|
||||
on_press:
|
||||
- if:
|
||||
condition:
|
||||
switch.is_off: mic_mute
|
||||
then:
|
||||
- voice_assistant.start:
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Listening Pulse"
|
||||
else:
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Error Flash"
|
||||
- delay: 500ms
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
red: 100%
|
||||
green: 30%
|
||||
blue: 0%
|
||||
brightness: 50%
|
||||
effect: "none"
|
||||
|
||||
# ===== Mic Mute Switch =====
|
||||
switch:
|
||||
- platform: template
|
||||
id: mic_mute
|
||||
name: "Microphone Mute"
|
||||
icon: "mdi:microphone-off"
|
||||
optimistic: true
|
||||
restore_mode: RESTORE_DEFAULT_OFF
|
||||
on_turn_on:
|
||||
- voice_assistant.stop:
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "none"
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
red: 100%
|
||||
green: 30%
|
||||
blue: 0%
|
||||
brightness: 50%
|
||||
on_turn_off:
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Idle Breathe"
|
||||
- voice_assistant.start:
|
||||
@@ -1,67 +0,0 @@
|
||||
# ===== LED Bar (WS2812B) =====
|
||||
light:
|
||||
- platform: esp32_rmt_led_strip
|
||||
id: led_bar
|
||||
chipset: WS2812
|
||||
rgb_order: GRB
|
||||
pin: ${led_pin}
|
||||
num_leds: ${led_num_leds}
|
||||
name: "Voice Satellite LEDs"
|
||||
default_transition_length: 200ms
|
||||
effects:
|
||||
- addressable_lambda:
|
||||
name: "Idle Breathe"
|
||||
update_interval: 50ms
|
||||
lambda: |-
|
||||
static float brightness = 0;
|
||||
static int direction = 1;
|
||||
brightness += direction * 0.02;
|
||||
if (brightness >= 1.0) { brightness = 1.0; direction = -1; }
|
||||
if (brightness <= 0.1) { brightness = 0.1; direction = 1; }
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
it[i] = Color(0, 0, (int)(40 * brightness));
|
||||
}
|
||||
- addressable_lambda:
|
||||
name: "Listening Pulse"
|
||||
update_interval: 30ms
|
||||
lambda: |-
|
||||
static int pos = 0;
|
||||
pos = (pos + 1) % (it.size() * 2);
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
int dist = abs(pos - i);
|
||||
if (pos >= it.size()) dist = abs((it.size() * 2 - pos) - i);
|
||||
int bright = max(0, 255 - dist * 60);
|
||||
it[i] = Color(0, bright, bright);
|
||||
}
|
||||
- addressable_lambda:
|
||||
name: "Processing"
|
||||
update_interval: 80ms
|
||||
lambda: |-
|
||||
static int offset = 0;
|
||||
offset = (offset + 1) % it.size();
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
if ((i + offset) % 2 == 0) {
|
||||
it[i] = Color(80, 0, 180);
|
||||
} else {
|
||||
it[i] = Color(0, 0, 0);
|
||||
}
|
||||
}
|
||||
- addressable_lambda:
|
||||
name: "Speaking"
|
||||
update_interval: 40ms
|
||||
lambda: |-
|
||||
static int wave = 0;
|
||||
wave = (wave + 1) % 16;
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
int bright = (sin((wave + i * 2) * 0.4) + 1) * 127;
|
||||
it[i] = Color(0, bright, 0);
|
||||
}
|
||||
- addressable_lambda:
|
||||
name: "Error Flash"
|
||||
update_interval: 100ms
|
||||
lambda: |-
|
||||
static bool on = false;
|
||||
on = !on;
|
||||
for (int i = 0; i < it.size(); i++) {
|
||||
it[i] = on ? Color(255, 0, 0) : Color(0, 0, 0);
|
||||
}
|
||||
@@ -1,53 +0,0 @@
|
||||
# ===== Web UI / HA Entities =====
|
||||
|
||||
script:
|
||||
- id: publish_current_time
|
||||
then:
|
||||
- lambda: |-
|
||||
auto time_now = id(homeassistant_time).now();
|
||||
id(current_time_sensor).publish_state(time_now.strftime("%H:%M"));
|
||||
|
||||
time:
|
||||
- platform: homeassistant
|
||||
id: homeassistant_time
|
||||
on_time_sync:
|
||||
- script.execute: publish_current_time
|
||||
on_time:
|
||||
- seconds: 0
|
||||
minutes: /1
|
||||
then:
|
||||
- script.execute: publish_current_time
|
||||
|
||||
text_sensor:
|
||||
- platform: template
|
||||
name: "Current device time"
|
||||
id: current_time_sensor
|
||||
icon: mdi:clock
|
||||
|
||||
number:
|
||||
- platform: template
|
||||
name: "Volume"
|
||||
id: volume_number
|
||||
icon: mdi:volume-high
|
||||
entity_category: config
|
||||
min_value: 0
|
||||
max_value: 100
|
||||
step: 5
|
||||
unit_of_measurement: "%"
|
||||
lambda: return id(volume_level) * 100.0f;
|
||||
set_action:
|
||||
- lambda: |-
|
||||
float vol = x / 100.0f;
|
||||
id(volume_level) = vol;
|
||||
id(spk).set_volume(vol);
|
||||
|
||||
select:
|
||||
- platform: logger
|
||||
name: "Logger Level"
|
||||
disabled_by_default: true
|
||||
|
||||
button:
|
||||
- platform: restart
|
||||
name: "Restart"
|
||||
icon: mdi:restart
|
||||
entity_category: config
|
||||
@@ -1,97 +0,0 @@
|
||||
# ===== Wake Word Sensitivity =====
|
||||
select:
|
||||
- platform: template
|
||||
name: "Wake Word Sensitivity"
|
||||
optimistic: true
|
||||
initial_option: "Slightly sensitive"
|
||||
restore_value: true
|
||||
entity_category: config
|
||||
icon: mdi:ear-hearing
|
||||
options:
|
||||
- Slightly sensitive
|
||||
- Moderately sensitive
|
||||
- Very sensitive
|
||||
on_value:
|
||||
lambda: |-
|
||||
if (x == "Slightly sensitive") {
|
||||
id(wake_word_model).set_probability_cutoff(247); // 0.97
|
||||
} else if (x == "Moderately sensitive") {
|
||||
id(wake_word_model).set_probability_cutoff(235); // 0.92
|
||||
} else if (x == "Very sensitive") {
|
||||
id(wake_word_model).set_probability_cutoff(212); // 0.83
|
||||
}
|
||||
|
||||
# ===== Voice Assistant =====
|
||||
voice_assistant:
|
||||
id: va
|
||||
microphone: mic
|
||||
speaker: spk
|
||||
micro_wake_word: mww
|
||||
noise_suppression_level: ${mic_noise_suppression_level}
|
||||
auto_gain: ${mic_auto_gain}
|
||||
volume_multiplier: ${mic_volume_multiplier}
|
||||
on_listening:
|
||||
- logger.log: "[VA] on_listening — mic open, sending audio to HA"
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Listening Pulse"
|
||||
on_stt_vad_end:
|
||||
- logger.log: "[VA] on_stt_vad_end — speech detected, processing"
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Processing"
|
||||
on_stt_end:
|
||||
- lambda: |-
|
||||
ESP_LOGI("va", "STT result: %s", x.c_str());
|
||||
- light.turn_off:
|
||||
id: led_bar
|
||||
on_tts_start:
|
||||
- lambda: |-
|
||||
ESP_LOGI("va", "TTS start: %s", x.c_str());
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Speaking"
|
||||
on_tts_end:
|
||||
- logger.log: "[VA] on_tts_end"
|
||||
on_tts_stream_end:
|
||||
- logger.log: "[VA] on_tts_stream_end"
|
||||
on_error:
|
||||
- lambda: |-
|
||||
ESP_LOGE("va", "VA error: code=%s, message=%s", code.c_str(), message.c_str());
|
||||
- if:
|
||||
condition:
|
||||
lambda: 'return code != "duplicate_wake_up_detected";'
|
||||
then:
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Error Flash"
|
||||
- delay: 2s
|
||||
- micro_wake_word.start:
|
||||
on_end:
|
||||
- logger.log: "[VA] on_end"
|
||||
- wait_until:
|
||||
not:
|
||||
voice_assistant.is_running:
|
||||
- light.turn_off:
|
||||
id: led_bar
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- voice_assistant.connected:
|
||||
- not:
|
||||
micro_wake_word.is_running:
|
||||
then:
|
||||
- micro_wake_word.start:
|
||||
on_idle:
|
||||
- logger.log: "[VA] on_idle"
|
||||
- light.turn_on:
|
||||
id: led_bar
|
||||
effect: "Idle Breathe"
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- voice_assistant.connected:
|
||||
- not:
|
||||
micro_wake_word.is_running:
|
||||
then:
|
||||
- micro_wake_word.start:
|
||||
@@ -1,135 +0,0 @@
|
||||
substitutions:
|
||||
device_name: jordyn-a1-camera
|
||||
friendly_name: "Jordyn A1 Camera"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
#cpu_frequency: 240MHZ
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: "PDB5LTKAcNWMC+MeE03/SKpqX42pcnITzmfwu761LjI="
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "d207583702e4b979e28a43f6f52d19ae"
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "Jordyn-A1-Camera"
|
||||
password: "jN4rzGokp5mt"
|
||||
|
||||
# Prevents aggressive reconnection attempts
|
||||
power_save_mode: none # Use 'light' for battery devices
|
||||
|
||||
# Slower but more reliable connection
|
||||
fast_connect: true
|
||||
|
||||
# Handle connection failures gracefully
|
||||
on_connect:
|
||||
- logger.log: "Wi-Fi connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Wi-Fi disconnected!"
|
||||
|
||||
# Reduce mDNS traffic
|
||||
mdns:
|
||||
disabled: false # Set to true if you use static IPs and don't need discovery
|
||||
|
||||
#captive_portal:
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
i2c:
|
||||
- id: camera_i2c
|
||||
sda: GPIO26
|
||||
scl: GPIO27
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
speed: 80MHz
|
||||
|
||||
esp32_camera:
|
||||
external_clock:
|
||||
pin: GPIO0
|
||||
frequency: 20MHz
|
||||
i2c_id: camera_i2c
|
||||
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
|
||||
vsync_pin: GPIO25
|
||||
href_pin: GPIO23
|
||||
pixel_clock_pin: GPIO22
|
||||
power_down_pin: GPIO32
|
||||
resolution: 1024x768
|
||||
jpeg_quality: 20
|
||||
vertical_flip: False
|
||||
horizontal_mirror: False
|
||||
max_framerate: 40 fps
|
||||
idle_framerate: 0.05 fps
|
||||
|
||||
# Image settings
|
||||
name: esp32-jordyn-a1-cam
|
||||
# brightness: 1
|
||||
# #contrast: 0
|
||||
# agc_gain_ceiling: 2X
|
||||
# agc_mode: MANUAL
|
||||
# agc_value: 5
|
||||
# ...
|
||||
|
||||
esp32_camera_web_server:
|
||||
- port: 80
|
||||
mode: STREAM
|
||||
- port: 8080
|
||||
mode: SNAPSHOT
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "jordyn-a1-cam-flash"
|
||||
pin: 4
|
||||
|
||||
# Diagnostic sensors
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "${device_name} WiFi Signal"
|
||||
update_interval: 60s
|
||||
|
||||
- platform: uptime
|
||||
name: "${device_name} Uptime"
|
||||
update_interval: 60s
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "${device_name} IP Address"
|
||||
ssid:
|
||||
name: "${device_name} Connected SSID"
|
||||
bssid:
|
||||
name: "${device_name} BSSID"
|
||||
|
||||
# Watchdog to auto-reboot if things go wrong
|
||||
interval:
|
||||
- interval: 5min
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- lambda: 'return millis() > 300000;' # >5 min uptime
|
||||
- not:
|
||||
wifi.connected:
|
||||
then:
|
||||
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
|
||||
- delay: 10s
|
||||
- lambda: 'App.safe_reboot();'
|
||||
@@ -1,122 +0,0 @@
|
||||
substitutions:
|
||||
device_name: laundry-litter-box-camera
|
||||
friendly_name: "Laundry Litter Box Camera"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
|
||||
logger:
|
||||
|
||||
api:
|
||||
encryption:
|
||||
key: !secret api_encryption_key
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: !secret ota_password
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
manual_ip:
|
||||
static_ip: 192.168.69.220
|
||||
gateway: 192.168.69.1
|
||||
subnet: 255.255.255.0
|
||||
ap:
|
||||
ssid: "laundry-litter-cam-fallback"
|
||||
password: !secret fallback_password
|
||||
# Prevents aggressive reconnection attempts
|
||||
power_save_mode: none # Use 'light' for battery devices
|
||||
|
||||
# Slower but more reliable connection
|
||||
fast_connect: true
|
||||
|
||||
# Handle connection failures gracefully
|
||||
on_connect:
|
||||
- logger.log: "Wi-Fi connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Wi-Fi disconnected!"
|
||||
|
||||
# Reduce mDNS traffic
|
||||
mdns:
|
||||
disabled: true # Set to true if you use static IPs and don't need discovery
|
||||
|
||||
# Diagnostic sensors
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "${device_name} WiFi Signal"
|
||||
update_interval: 60s
|
||||
|
||||
- platform: uptime
|
||||
name: "${device_name} Uptime"
|
||||
update_interval: 60s
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "${device_name} IP Address"
|
||||
ssid:
|
||||
name: "${device_name} Connected SSID"
|
||||
bssid:
|
||||
name: "${device_name} BSSID"
|
||||
|
||||
# Watchdog to auto-reboot if things go wrong
|
||||
interval:
|
||||
- interval: 5min
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- lambda: 'return millis() > 300000;' # >5 min uptime
|
||||
- not:
|
||||
wifi.connected:
|
||||
then:
|
||||
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
|
||||
- delay: 10s
|
||||
- lambda: 'App.safe_reboot();'
|
||||
|
||||
captive_portal:
|
||||
|
||||
i2c:
|
||||
- id: camera_i2c
|
||||
sda: GPIO26
|
||||
scl: GPIO27
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
speed: 80MHz
|
||||
|
||||
esp32_camera:
|
||||
external_clock:
|
||||
pin: GPIO0
|
||||
frequency: 20MHz
|
||||
i2c_id: camera_i2c
|
||||
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
|
||||
vsync_pin: GPIO25
|
||||
href_pin: GPIO23
|
||||
pixel_clock_pin: GPIO22
|
||||
power_down_pin: GPIO32
|
||||
resolution: 1024x768
|
||||
jpeg_quality: 15
|
||||
vertical_flip: true
|
||||
horizontal_mirror: false
|
||||
max_framerate: 20 fps
|
||||
idle_framerate: 0.05 fps
|
||||
name: ${device_name}
|
||||
|
||||
esp32_camera_web_server:
|
||||
- port: 8080
|
||||
mode: STREAM
|
||||
- port: 8081
|
||||
mode: SNAPSHOT
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "${device_name}-flash"
|
||||
pin: 4
|
||||
@@ -1,33 +0,0 @@
|
||||
packages:
|
||||
jarvis-satellite: !include jarvis-satellite/jarvis-satellite-base.yaml
|
||||
|
||||
### Change the following for each satellite device, e.g. "jarvis-satellite-office" and "jarvis-satellite-bedroom" and to match each ESP board pinout
|
||||
substitutions:
|
||||
flash_size: "8MB"
|
||||
psram_mode: "quad"
|
||||
psram_speed: "80MHz"
|
||||
i2s_in_lrclk_pin: GPIO6
|
||||
i2s_in_bclk_pin: GPIO7
|
||||
mic_din_pin: GPIO4
|
||||
i2s_out_lrclk_pin: GPIO45
|
||||
i2s_out_bclk_pin: GPIO46
|
||||
speaker_dout_pin: GPIO8
|
||||
led_pin: GPIO16
|
||||
led_num_leds: "8"
|
||||
btn_vol_up_pin: GPIO1
|
||||
btn_vol_down_pin: GPIO2
|
||||
btn_mute_pin: GPIO3
|
||||
btn_wake_pin: GPIO10
|
||||
cpu_frequency: "240MHz"
|
||||
|
||||
|
||||
esphome:
|
||||
name: office-jarvis-satellite
|
||||
friendly_name: "Office Jarvis"
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
ap:
|
||||
ssid: "Jarvis-Satellite-AP"
|
||||
password: !secret wifi_password
|
||||
@@ -1,133 +0,0 @@
|
||||
substitutions:
|
||||
device_name: polly-a1-mini-camera
|
||||
friendly_name: "Polly's A1 Camera"
|
||||
|
||||
esphome:
|
||||
name: ${device_name}
|
||||
friendly_name: ${friendly_name}
|
||||
|
||||
esp32:
|
||||
board: esp32dev
|
||||
framework:
|
||||
type: esp-idf
|
||||
# minimum_chip_revision: 3.1
|
||||
|
||||
# Enable logging
|
||||
logger:
|
||||
level: INFO
|
||||
|
||||
# Enable Home Assistant API
|
||||
api:
|
||||
encryption:
|
||||
key: "iLF5QCgTVCwgaA5vjTGez03Dfjvns8JvkcwmUN4NXR8="
|
||||
|
||||
ota:
|
||||
- platform: esphome
|
||||
password: "58f86a5e60abe60886cec2f65a1d3da5"
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
manual_ip:
|
||||
static_ip: 192.168.69.194
|
||||
gateway: 192.168.69.1
|
||||
subnet: 255.255.255.0
|
||||
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||
ap:
|
||||
ssid: "${friendly_name} Fallback"
|
||||
password: !secret wifi_password
|
||||
|
||||
# Prevents aggressive reconnection attempts
|
||||
power_save_mode: none # Use 'light' for battery devices
|
||||
|
||||
# Slower but more reliable connection
|
||||
fast_connect: true
|
||||
|
||||
# Handle connection failures gracefully
|
||||
on_connect:
|
||||
- logger.log: "Wi-Fi connected!"
|
||||
on_disconnect:
|
||||
- logger.log: "Wi-Fi disconnected!"
|
||||
|
||||
# Reduce mDNS traffic
|
||||
mdns:
|
||||
disabled: true # Set to true if you use static IPs and don't need discovery
|
||||
|
||||
#captive_portal:
|
||||
|
||||
web_server:
|
||||
port: 80
|
||||
|
||||
i2c:
|
||||
- id: camera_i2c
|
||||
sda: GPIO26
|
||||
scl: GPIO27
|
||||
|
||||
psram:
|
||||
mode: quad
|
||||
speed: 80MHz
|
||||
|
||||
esp32_camera:
|
||||
external_clock:
|
||||
pin: GPIO0
|
||||
frequency: 20MHz
|
||||
i2c_id: camera_i2c
|
||||
data_pins: [GPIO5, GPIO18, GPIO19, GPIO21, GPIO36, GPIO39, GPIO34, GPIO35]
|
||||
vsync_pin: GPIO25
|
||||
href_pin: GPIO23
|
||||
pixel_clock_pin: GPIO22
|
||||
power_down_pin: GPIO32
|
||||
resolution: 1024X768
|
||||
jpeg_quality: 20
|
||||
vertical_flip: False
|
||||
horizontal_mirror: True
|
||||
max_framerate: 20 fps
|
||||
idle_framerate: 0.05 fps
|
||||
|
||||
# Image settings
|
||||
name: ${device_name}
|
||||
|
||||
esp32_camera_web_server:
|
||||
- port: 8080
|
||||
mode: STREAM
|
||||
- port: 8081
|
||||
mode: SNAPSHOT
|
||||
|
||||
switch:
|
||||
- platform: gpio
|
||||
name: "${device_name}-flash"
|
||||
pin: 4
|
||||
|
||||
# Diagnostic sensors
|
||||
sensor:
|
||||
- platform: wifi_signal
|
||||
name: "${device_name} WiFi Signal"
|
||||
update_interval: 60s
|
||||
|
||||
- platform: uptime
|
||||
name: "${device_name} Uptime"
|
||||
update_interval: 60s
|
||||
|
||||
text_sensor:
|
||||
- platform: wifi_info
|
||||
ip_address:
|
||||
name: "${device_name} IP Address"
|
||||
ssid:
|
||||
name: "${device_name} Connected SSID"
|
||||
bssid:
|
||||
name: "${device_name} BSSID"
|
||||
|
||||
# Watchdog to auto-reboot if things go wrong
|
||||
interval:
|
||||
- interval: 5min
|
||||
then:
|
||||
- if:
|
||||
condition:
|
||||
and:
|
||||
- lambda: 'return millis() > 300000;' # >5 min uptime
|
||||
- not:
|
||||
wifi.connected:
|
||||
then:
|
||||
- logger.log: "Wi-Fi not connected for 5 min, rebooting..."
|
||||
- delay: 10s
|
||||
- lambda: 'App.safe_reboot();'
|
||||
@@ -1,10 +0,0 @@
|
||||
packages:
|
||||
respeaker-satellite: !include respeaker-satellite/respeaker-satellite-base.yaml
|
||||
|
||||
esphome:
|
||||
name: respeaker-satellite-fr
|
||||
friendly_name: "Respeaker Satellite Family Room"
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
@@ -1,10 +0,0 @@
|
||||
packages:
|
||||
respeaker-satellite: !include respeaker-satellite/respeaker-satellite-base.yaml
|
||||
|
||||
esphome:
|
||||
name: respeaker-satellite-lr
|
||||
friendly_name: "Respeaker Satellite Living Room"
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
@@ -1,10 +0,0 @@
|
||||
packages:
|
||||
respeaker-satellite: !include respeaker-satellite/respeaker-satellite-base.yaml
|
||||
|
||||
esphome:
|
||||
name: respeaker-satellite-mbr
|
||||
friendly_name: "Respeaker Satellite Master Bedroom"
|
||||
|
||||
wifi:
|
||||
ssid: !secret wifi_iot_ssid
|
||||
password: !secret wifi_password
|
||||
File diff suppressed because it is too large
Load Diff
Reference in New Issue
Block a user