Added Raspiaudio Muse Luxe again as a sendspin player.
This commit is contained in:
@@ -0,0 +1,50 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
|
import esphome.config_validation as cv
|
||||||
|
from esphome.components import binary_sensor, i2c, sensor
|
||||||
|
from esphome.const import CONF_ID, ENTITY_CATEGORY_DIAGNOSTIC
|
||||||
|
|
||||||
|
DEPENDENCIES = ["i2c"]
|
||||||
|
AUTO_LOAD = ["sensor", "binary_sensor"]
|
||||||
|
|
||||||
|
ns = cg.esphome_ns.namespace("luxe_ip5306")
|
||||||
|
LuxeIP5306 = ns.class_("LuxeIP5306", cg.PollingComponent, i2c.I2CDevice)
|
||||||
|
|
||||||
|
CONFIG_SCHEMA = (
|
||||||
|
cv.Schema(
|
||||||
|
{
|
||||||
|
cv.GenerateID(): cv.declare_id(LuxeIP5306),
|
||||||
|
cv.Required("available"): binary_sensor.binary_sensor_schema(
|
||||||
|
device_class="connectivity", entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||||
|
),
|
||||||
|
cv.Required("charging"): binary_sensor.binary_sensor_schema(
|
||||||
|
device_class="battery_charging"
|
||||||
|
),
|
||||||
|
cv.Required("full"): binary_sensor.binary_sensor_schema(),
|
||||||
|
cv.Required("charge_allowed"): binary_sensor.binary_sensor_schema(
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||||
|
),
|
||||||
|
cv.Required("level"): sensor.sensor_schema(
|
||||||
|
unit_of_measurement="%", accuracy_decimals=0, device_class="battery",
|
||||||
|
state_class="measurement", entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||||
|
),
|
||||||
|
cv.Required("current_limit"): sensor.sensor_schema(
|
||||||
|
unit_of_measurement="mA", accuracy_decimals=0,
|
||||||
|
entity_category=ENTITY_CATEGORY_DIAGNOSTIC
|
||||||
|
),
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.extend(cv.polling_component_schema("10s"))
|
||||||
|
.extend(i2c.i2c_device_schema(0x75))
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
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)
|
||||||
|
for key in ("available", "charging", "full", "charge_allowed"):
|
||||||
|
entity = await binary_sensor.new_binary_sensor(config[key])
|
||||||
|
cg.add(getattr(var, f"set_{key}")(entity))
|
||||||
|
for key in ("level", "current_limit"):
|
||||||
|
entity = await sensor.new_sensor(config[key])
|
||||||
|
cg.add(getattr(var, f"set_{key}")(entity))
|
||||||
@@ -0,0 +1,108 @@
|
|||||||
|
#pragma once
|
||||||
|
|
||||||
|
#include <cmath>
|
||||||
|
#include "esphome/core/component.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
#include "esphome/components/i2c/i2c.h"
|
||||||
|
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||||
|
#include "esphome/components/sensor/sensor.h"
|
||||||
|
|
||||||
|
namespace esphome::luxe_ip5306 {
|
||||||
|
|
||||||
|
// Optional IP5306-I2C on recent Luxe boards. Never changes power settings at boot.
|
||||||
|
class LuxeIP5306 : public PollingComponent, public i2c::I2CDevice {
|
||||||
|
public:
|
||||||
|
void set_available(binary_sensor::BinarySensor *s) { available_ = s; }
|
||||||
|
void set_charging(binary_sensor::BinarySensor *s) { charging_ = s; }
|
||||||
|
void set_full(binary_sensor::BinarySensor *s) { full_ = s; }
|
||||||
|
void set_charge_allowed(binary_sensor::BinarySensor *s) { charge_allowed_ = s; }
|
||||||
|
void set_level(sensor::Sensor *s) { level_ = s; }
|
||||||
|
void set_current_limit(sensor::Sensor *s) { current_limit_ = s; }
|
||||||
|
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||||
|
|
||||||
|
void setup() override { update(); }
|
||||||
|
|
||||||
|
void update() override {
|
||||||
|
uint8_t control, charge, full, limit, gauge;
|
||||||
|
// Address-only ACK probe, then individual register reads. No assumption
|
||||||
|
// that the chip supports register auto-increment.
|
||||||
|
if (write(nullptr, 0) != i2c::ERROR_OK ||
|
||||||
|
!read_byte(0x00, &control) || !read_byte(0x70, &charge) ||
|
||||||
|
!read_byte(0x71, &full) || !read_byte(0x24, &limit)) {
|
||||||
|
unavailable_();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
valid_ = true;
|
||||||
|
charge_enabled_ = (control & 0x10) != 0;
|
||||||
|
available_->publish_state(true);
|
||||||
|
charge_allowed_->publish_state(charge_enabled_);
|
||||||
|
const bool full_flag = (full & 0x08) != 0;
|
||||||
|
full_->publish_state(full_flag);
|
||||||
|
charging_->publish_state(charge_enabled_ && (charge & 0x08) && !full_flag);
|
||||||
|
// Configured current, NOT measured current. Keep it separate from the
|
||||||
|
// charging state: 0x70 alone cannot establish USB presence in every mode.
|
||||||
|
current_limit_->publish_state(50 + 100 * (limit & 0x1F));
|
||||||
|
// Gauge is optional: undocumented on some revisions. Failure/unknown
|
||||||
|
// codes must never be presented as an empty battery.
|
||||||
|
float percent = NAN;
|
||||||
|
if (read_byte(0x78, &gauge)) {
|
||||||
|
switch (gauge >> 4) {
|
||||||
|
case 0x0: percent = 100; break;
|
||||||
|
case 0x8: percent = 75; break;
|
||||||
|
case 0xC: percent = 50; break;
|
||||||
|
case 0xE: percent = 25; break;
|
||||||
|
case 0xF: percent = 0; break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
level_->publish_state(percent);
|
||||||
|
}
|
||||||
|
|
||||||
|
optional<bool> charge_enabled() const {
|
||||||
|
if (!valid_) return {};
|
||||||
|
return charge_enabled_;
|
||||||
|
}
|
||||||
|
|
||||||
|
void set_charge_enabled(bool enabled) {
|
||||||
|
uint8_t before;
|
||||||
|
// Read afresh even if the last poll succeeded. Modify only bit 4, leaving
|
||||||
|
// boost, key behaviour and all reserved bits untouched.
|
||||||
|
if (write(nullptr, 0) != i2c::ERROR_OK || !read_byte(0x00, &before)) {
|
||||||
|
unavailable_();
|
||||||
|
ESP_LOGW("luxe_ip5306", "Recharge command rejected: circuit unavailable");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
const uint8_t after = enabled ? (before | 0x10) : (before & ~0x10);
|
||||||
|
if (after != before && !write_byte(0x00, after)) {
|
||||||
|
unavailable_();
|
||||||
|
ESP_LOGW("luxe_ip5306", "Recharge command failed");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
update(); // Read-back is authoritative; the switch is not optimistic.
|
||||||
|
if (valid_ && charge_enabled_ != enabled)
|
||||||
|
ESP_LOGW("luxe_ip5306", "Recharge command not confirmed by circuit");
|
||||||
|
}
|
||||||
|
|
||||||
|
protected:
|
||||||
|
void unavailable_() {
|
||||||
|
valid_ = false;
|
||||||
|
available_->publish_state(false);
|
||||||
|
charging_->invalidate_state();
|
||||||
|
full_->invalidate_state();
|
||||||
|
charge_allowed_->invalidate_state();
|
||||||
|
level_->publish_state(NAN);
|
||||||
|
current_limit_->publish_state(NAN);
|
||||||
|
// No mark_failed(): an older board must still play audio, and a device
|
||||||
|
// temporarily asleep or disconnected must be detected on the next poll.
|
||||||
|
}
|
||||||
|
|
||||||
|
binary_sensor::BinarySensor *available_{nullptr};
|
||||||
|
binary_sensor::BinarySensor *charging_{nullptr};
|
||||||
|
binary_sensor::BinarySensor *full_{nullptr};
|
||||||
|
binary_sensor::BinarySensor *charge_allowed_{nullptr};
|
||||||
|
sensor::Sensor *level_{nullptr};
|
||||||
|
sensor::Sensor *current_limit_{nullptr};
|
||||||
|
bool valid_{false};
|
||||||
|
bool charge_enabled_{false};
|
||||||
|
};
|
||||||
|
} // namespace esphome::luxe_ip5306
|
||||||
@@ -0,0 +1,485 @@
|
|||||||
|
substitutions:
|
||||||
|
startup_sound_file: https://github.com/RASPIAUDIO/esphomeLuxe/raw/refs/heads/main/wav/sounds_timer_finished.wav
|
||||||
|
device_description: "Raspiaudio Muse Luxe Music player for Music Assistant using sendspin"
|
||||||
|
name: "muse-luxe-music"
|
||||||
|
friendly_name: "Raspiaudio Muse Luxe Music"
|
||||||
|
|
||||||
|
|
||||||
|
esphome:
|
||||||
|
comment: ${device_description}
|
||||||
|
name: ${name}
|
||||||
|
friendly_name: ${friendly_name}
|
||||||
|
min_version: 2026.8.2
|
||||||
|
name_add_mac_suffix: true
|
||||||
|
on_boot:
|
||||||
|
priority: -100
|
||||||
|
then:
|
||||||
|
# GPIO21 commande la mise en sourdine de l'amplificateur de la Luxe.
|
||||||
|
- output.turn_off: dac_mute
|
||||||
|
# Laisse le mixer terminer une annonce avant la reprise d'un flux MA.
|
||||||
|
- speaker_source.set_playlist_delay:
|
||||||
|
id: external_media_player
|
||||||
|
pipeline: announcement
|
||||||
|
delay: 500ms
|
||||||
|
- speaker_source.set_playlist_delay:
|
||||||
|
id: external_media_player
|
||||||
|
pipeline: media
|
||||||
|
delay: 1500ms
|
||||||
|
- script.execute: led_red
|
||||||
|
project:
|
||||||
|
name: raspiaudio.music-assistant
|
||||||
|
version: "2026.9.3"
|
||||||
|
|
||||||
|
esp32:
|
||||||
|
variant: esp32
|
||||||
|
framework:
|
||||||
|
type: esp-idf
|
||||||
|
advanced:
|
||||||
|
include_builtin_idf_components:
|
||||||
|
- esp_http_client
|
||||||
|
sram1_as_iram: true
|
||||||
|
|
||||||
|
logger:
|
||||||
|
level: warn
|
||||||
|
|
||||||
|
api:
|
||||||
|
encryption:
|
||||||
|
key: !secret apikey
|
||||||
|
# La lecture Sendspin doit continuer même sans client Home Assistant connecté.
|
||||||
|
reboot_timeout: 0s
|
||||||
|
on_client_connected:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return !id(boot_sound_played);
|
||||||
|
then:
|
||||||
|
- lambda: id(boot_sound_played) = true;
|
||||||
|
# Laisse MA restaurer son groupe avant de décider si l'annonce peut jouer.
|
||||||
|
- delay: 5s
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: |-
|
||||||
|
using media_player::MediaPlayerState;
|
||||||
|
return id(startup_sound_switch).state &&
|
||||||
|
id(external_media_player).state != MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING &&
|
||||||
|
id(external_media_player).state != MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING;
|
||||||
|
then:
|
||||||
|
- script.execute:
|
||||||
|
id: play_sound
|
||||||
|
priority: true
|
||||||
|
sound_file: ready_sound
|
||||||
|
|
||||||
|
wifi:
|
||||||
|
ssid: "Voltage-legacy"
|
||||||
|
password: !secret voltage_legacy_psk
|
||||||
|
#use_address: ${name}.home
|
||||||
|
power_save_mode: high
|
||||||
|
fast_connect: on
|
||||||
|
min_auth_mode: WPA2
|
||||||
|
|
||||||
|
# Enable fallback hotspot (captive portal) in case wifi connection fails
|
||||||
|
ap:
|
||||||
|
ssid: "Raspiaudio Fallback Hotspot"
|
||||||
|
password: !secret fallback_psk
|
||||||
|
|
||||||
|
psram:
|
||||||
|
mode: quad
|
||||||
|
speed: 80MHz
|
||||||
|
|
||||||
|
ota:
|
||||||
|
- platform: esphome
|
||||||
|
id: ota_esphome
|
||||||
|
encryption:
|
||||||
|
- platform: http_request
|
||||||
|
id: ota_http_request
|
||||||
|
|
||||||
|
http_request:
|
||||||
|
|
||||||
|
captive_portal:
|
||||||
|
|
||||||
|
improv_serial:
|
||||||
|
|
||||||
|
external_components:
|
||||||
|
- source:
|
||||||
|
type: local
|
||||||
|
path: components
|
||||||
|
components: [luxe_ip5306]
|
||||||
|
# Initialisation spécifique du codec de la museLuxe (sorties différentielles
|
||||||
|
# et horloge MCLK sur GPIO0). Sendspin et la chaîne audio sont désormais natifs.
|
||||||
|
- source:
|
||||||
|
type: git
|
||||||
|
url: https://github.com/RASPIAUDIO/esphomeLuxe
|
||||||
|
ref: 192372ff59b560a74aa547dc26af609918fcc9d6
|
||||||
|
components: [es8388]
|
||||||
|
|
||||||
|
i2c:
|
||||||
|
sda: GPIO18
|
||||||
|
scl: GPIO23
|
||||||
|
frequency: 100kHz
|
||||||
|
id: bus_a
|
||||||
|
|
||||||
|
es8388:
|
||||||
|
|
||||||
|
i2s_audio:
|
||||||
|
- id: i2s_audio_bus
|
||||||
|
i2s_lrclk_pin: GPIO25
|
||||||
|
i2s_bclk_pin: GPIO5
|
||||||
|
#####################
|
||||||
|
# Internal Components
|
||||||
|
output:
|
||||||
|
- platform: gpio
|
||||||
|
id: dac_mute
|
||||||
|
pin:
|
||||||
|
number: GPIO21
|
||||||
|
inverted: true
|
||||||
|
mode:
|
||||||
|
output: true
|
||||||
|
speaker:
|
||||||
|
- platform: i2s_audio
|
||||||
|
id: box_speaker
|
||||||
|
i2s_audio_id: i2s_audio_bus
|
||||||
|
i2s_dout_pin: GPIO26
|
||||||
|
dac_type: external
|
||||||
|
channel: stereo
|
||||||
|
sample_rate: 48000
|
||||||
|
buffer_duration: 300ms
|
||||||
|
|
||||||
|
- platform: mixer
|
||||||
|
id: mixing_speaker
|
||||||
|
output_speaker: box_speaker
|
||||||
|
num_channels: 2
|
||||||
|
source_speakers:
|
||||||
|
- id: announcement_mixer_input
|
||||||
|
- id: media_mixer_input
|
||||||
|
|
||||||
|
- platform: resampler
|
||||||
|
id: media_resampling_input
|
||||||
|
output_speaker: media_mixer_input
|
||||||
|
sample_rate: 48000
|
||||||
|
|
||||||
|
- platform: resampler
|
||||||
|
id: announcement_resampling_input
|
||||||
|
output_speaker: announcement_mixer_input
|
||||||
|
sample_rate: 48000
|
||||||
|
|
||||||
|
sendspin:
|
||||||
|
id: sendspin_hub
|
||||||
|
|
||||||
|
audio_file:
|
||||||
|
- id: ready_sound
|
||||||
|
file: ${startup_sound_file}
|
||||||
|
|
||||||
|
media_source:
|
||||||
|
- platform: sendspin
|
||||||
|
id: sendspin_source
|
||||||
|
buffer_size: 2000000
|
||||||
|
- platform: audio_file
|
||||||
|
id: file_source
|
||||||
|
|
||||||
|
media_player:
|
||||||
|
- platform: sendspin
|
||||||
|
id: sendspin_media_player
|
||||||
|
name: museLuxe
|
||||||
|
volume_increment: 2%
|
||||||
|
- platform: speaker_source
|
||||||
|
name: museLuxe Audio
|
||||||
|
id: external_media_player
|
||||||
|
internal: true
|
||||||
|
|
||||||
|
announcement_pipeline:
|
||||||
|
speaker: announcement_resampling_input
|
||||||
|
sources:
|
||||||
|
- file_source
|
||||||
|
format: FLAC
|
||||||
|
num_channels: 1
|
||||||
|
sample_rate: 48000
|
||||||
|
media_pipeline:
|
||||||
|
speaker: media_resampling_input
|
||||||
|
sources:
|
||||||
|
- sendspin_source
|
||||||
|
format: FLAC
|
||||||
|
num_channels: 2
|
||||||
|
sample_rate: 48000
|
||||||
|
|
||||||
|
volume_increment: 0.05
|
||||||
|
volume_min: 0.05
|
||||||
|
volume_max: 0.95
|
||||||
|
on_state:
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
using media_player::MediaPlayerState;
|
||||||
|
auto state = id(external_media_player).state;
|
||||||
|
if (
|
||||||
|
state == MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING ||
|
||||||
|
state == MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING
|
||||||
|
) {
|
||||||
|
id(led_green).execute();
|
||||||
|
} else {
|
||||||
|
id(led_red).execute();
|
||||||
|
}
|
||||||
|
script:
|
||||||
|
- id: update_battery_led
|
||||||
|
mode: queued
|
||||||
|
max_runs: 2
|
||||||
|
then:
|
||||||
|
- lambda: |-
|
||||||
|
const bool critical = id(battery_critical).state;
|
||||||
|
if (critical && (!id(led).remote_values.is_on() || id(led).get_effect_name() != "Battery alert")) {
|
||||||
|
auto call = id(led).turn_on();
|
||||||
|
call.set_effect("Battery alert");
|
||||||
|
call.set_brightness(1.0f);
|
||||||
|
call.perform();
|
||||||
|
} else if (!critical && id(led).get_effect_name() == "Battery alert") {
|
||||||
|
auto call = id(led).turn_on();
|
||||||
|
call.set_effect("None");
|
||||||
|
call.perform();
|
||||||
|
}
|
||||||
|
- id: play_sound
|
||||||
|
parameters:
|
||||||
|
priority: bool
|
||||||
|
sound_file: string
|
||||||
|
then:
|
||||||
|
- if:
|
||||||
|
condition:
|
||||||
|
lambda: return priority;
|
||||||
|
then:
|
||||||
|
- media_player.stop:
|
||||||
|
id: external_media_player
|
||||||
|
announcement: true
|
||||||
|
- lambda: |-
|
||||||
|
if ( (id(external_media_player).state != media_player::MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING ) || priority) {
|
||||||
|
id(external_media_player)
|
||||||
|
->make_call()
|
||||||
|
.set_media_url("audio-file://" + sound_file)
|
||||||
|
.set_announcement(true)
|
||||||
|
.perform();
|
||||||
|
}
|
||||||
|
- id: led_red
|
||||||
|
then:
|
||||||
|
- light.turn_on:
|
||||||
|
id: led
|
||||||
|
red: 100%
|
||||||
|
green: 0%
|
||||||
|
blue: 0%
|
||||||
|
brightness: 100%
|
||||||
|
|
||||||
|
- id: led_green
|
||||||
|
then:
|
||||||
|
- light.turn_on:
|
||||||
|
id: led
|
||||||
|
red: 0%
|
||||||
|
green: 100%
|
||||||
|
blue: 0%
|
||||||
|
brightness: 100%
|
||||||
|
|
||||||
|
switch:
|
||||||
|
- platform: template
|
||||||
|
id: ip5306_charge_switch
|
||||||
|
name: "Allow charging"
|
||||||
|
entity_category: config
|
||||||
|
icon: mdi:battery-charging
|
||||||
|
restore_mode: DISABLED
|
||||||
|
optimistic: false
|
||||||
|
lambda: return id(battery_manager).charge_enabled();
|
||||||
|
turn_on_action:
|
||||||
|
- lambda: id(battery_manager).set_charge_enabled(true);
|
||||||
|
turn_off_action:
|
||||||
|
- lambda: id(battery_manager).set_charge_enabled(false);
|
||||||
|
- platform: template
|
||||||
|
id: startup_sound_switch
|
||||||
|
name: Startup sound
|
||||||
|
icon: "mdi:card-text-outline"
|
||||||
|
entity_category: config
|
||||||
|
optimistic: true
|
||||||
|
restore_mode: RESTORE_DEFAULT_ON
|
||||||
|
|
||||||
|
globals:
|
||||||
|
- id: boot_sound_played
|
||||||
|
type: bool
|
||||||
|
restore_value: no
|
||||||
|
initial_value: "false"
|
||||||
|
|
||||||
|
binary_sensor:
|
||||||
|
- platform: template
|
||||||
|
id: battery_critical
|
||||||
|
name: Battery low
|
||||||
|
device_class: battery
|
||||||
|
entity_category: diagnostic
|
||||||
|
lambda: |-
|
||||||
|
if (!id(battery_percent).has_state() || !std::isfinite(id(battery_percent).state)) return {};
|
||||||
|
const float percent = id(battery_percent).state;
|
||||||
|
const float threshold = id(battery_alert_threshold).state;
|
||||||
|
if (percent < threshold) return true;
|
||||||
|
if (percent >= threshold + 5.0f) return false;
|
||||||
|
return {};
|
||||||
|
on_state:
|
||||||
|
- script.execute: update_battery_led
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO12
|
||||||
|
inverted: true
|
||||||
|
mode:
|
||||||
|
input: true
|
||||||
|
output: false
|
||||||
|
open_drain: false
|
||||||
|
pullup: true
|
||||||
|
pulldown: false
|
||||||
|
drive_strength: 20.0
|
||||||
|
name: Play or Pause
|
||||||
|
on_press:
|
||||||
|
then:
|
||||||
|
- media_player.toggle: sendspin_media_player
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO19
|
||||||
|
inverted: true
|
||||||
|
mode:
|
||||||
|
input: true
|
||||||
|
pullup: true
|
||||||
|
name: Volume Up
|
||||||
|
on_click:
|
||||||
|
- media_player.volume_up:
|
||||||
|
id: sendspin_media_player
|
||||||
|
|
||||||
|
- platform: gpio
|
||||||
|
pin:
|
||||||
|
number: GPIO32
|
||||||
|
inverted: true
|
||||||
|
mode:
|
||||||
|
input: true
|
||||||
|
pullup: true
|
||||||
|
name: Volume Down
|
||||||
|
on_click:
|
||||||
|
- media_player.volume_down:
|
||||||
|
id: sendspin_media_player
|
||||||
|
|
||||||
|
light:
|
||||||
|
- platform: esp32_rmt_led_strip
|
||||||
|
id: led
|
||||||
|
name: LED light
|
||||||
|
disabled_by_default: false
|
||||||
|
entity_category: config
|
||||||
|
pin: GPIO22
|
||||||
|
default_transition_length: 0s
|
||||||
|
chipset: WS2812
|
||||||
|
num_leds: 1
|
||||||
|
channel_colors: GRB
|
||||||
|
# Conserve l'alerte après une commande HA ou un changement lecture/arrêt.
|
||||||
|
on_state:
|
||||||
|
- script.execute: update_battery_led
|
||||||
|
effects:
|
||||||
|
- addressable_lambda:
|
||||||
|
name: "Battery alert"
|
||||||
|
update_interval: 500ms
|
||||||
|
lambda: |-
|
||||||
|
static bool lit = false;
|
||||||
|
if (initial_run) lit = false;
|
||||||
|
lit = !lit;
|
||||||
|
it.all() = lit ? current_color : Color::BLACK;
|
||||||
|
- pulse:
|
||||||
|
name: "Slow Pulse"
|
||||||
|
transition_length: 250ms
|
||||||
|
update_interval: 250ms
|
||||||
|
min_brightness: 50%
|
||||||
|
max_brightness: 100%
|
||||||
|
- pulse:
|
||||||
|
name: "Fast Pulse"
|
||||||
|
transition_length: 100ms
|
||||||
|
update_interval: 100ms
|
||||||
|
min_brightness: 50%
|
||||||
|
max_brightness: 100%
|
||||||
|
|
||||||
|
sensor:
|
||||||
|
- platform: adc
|
||||||
|
pin: GPIO33
|
||||||
|
id: battery_voltage
|
||||||
|
name: Battery voltage
|
||||||
|
icon: "mdi:battery-outline"
|
||||||
|
update_interval: 15s
|
||||||
|
accuracy_decimals: 3
|
||||||
|
attenuation: 12db
|
||||||
|
raw: true
|
||||||
|
filters:
|
||||||
|
- multiply: 0.00173913 # 2300 -> 4, for attenuation 11db, based on Olivier's code
|
||||||
|
- exponential_moving_average:
|
||||||
|
alpha: 0.2
|
||||||
|
send_every: 2
|
||||||
|
- delta: 0.002
|
||||||
|
on_value:
|
||||||
|
- sensor.template.publish:
|
||||||
|
id: battery_percent
|
||||||
|
state: !lambda return x;
|
||||||
|
- platform: template
|
||||||
|
id: battery_percent
|
||||||
|
name: Battery level
|
||||||
|
device_class: battery
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
accuracy_decimals: 0
|
||||||
|
state_class: measurement
|
||||||
|
entity_category: diagnostic
|
||||||
|
update_interval: never
|
||||||
|
# Même estimation tension/charge que la Radio, après calibration Luxe.
|
||||||
|
filters:
|
||||||
|
- calibrate_polynomial:
|
||||||
|
degree: 3
|
||||||
|
datapoints:
|
||||||
|
- 4.20 -> 100.0
|
||||||
|
- 4.13 -> 97.1
|
||||||
|
- 4.10 -> 94.2
|
||||||
|
- 4.07 -> 88.4
|
||||||
|
- 4.05 -> 82.7
|
||||||
|
- 4.04 -> 76.9
|
||||||
|
- 4.04 -> 71.1
|
||||||
|
- 4.01 -> 65.3
|
||||||
|
- 3.99 -> 59.5
|
||||||
|
- 3.95 -> 53.8
|
||||||
|
- 3.93 -> 48.0
|
||||||
|
- 3.91 -> 42.2
|
||||||
|
- 3.88 -> 36.4
|
||||||
|
- 3.86 -> 30.6
|
||||||
|
- 3.84 -> 24.9
|
||||||
|
- 3.82 -> 19.1
|
||||||
|
- 3.76 -> 13.3
|
||||||
|
- 3.73 -> 10.4
|
||||||
|
- 3.70 -> 7.5
|
||||||
|
- 3.64 -> 4.6
|
||||||
|
- 3.50 -> 1.7
|
||||||
|
- 3.00 -> 0.0
|
||||||
|
- lambda: return clamp(x, 0.0f, 100.0f);
|
||||||
|
- platform: wifi_signal
|
||||||
|
name: "museLuxe WiFi signal"
|
||||||
|
update_interval: 10s
|
||||||
|
|
||||||
|
# Paramètre logiciel : conserve une hystérésis fixe de 5 points.
|
||||||
|
number:
|
||||||
|
- platform: template
|
||||||
|
id: battery_alert_threshold
|
||||||
|
name: "Low battery threshold"
|
||||||
|
entity_category: config
|
||||||
|
unit_of_measurement: "%"
|
||||||
|
min_value: 5
|
||||||
|
max_value: 50
|
||||||
|
step: 1
|
||||||
|
initial_value: 20
|
||||||
|
restore_value: true
|
||||||
|
optimistic: true
|
||||||
|
mode: box
|
||||||
|
|
||||||
|
luxe_ip5306:
|
||||||
|
id: battery_manager
|
||||||
|
i2c_id: bus_a
|
||||||
|
address: 0x75
|
||||||
|
update_interval: 10s
|
||||||
|
available:
|
||||||
|
name: "IP5306 available"
|
||||||
|
id: ip5306_available
|
||||||
|
charging:
|
||||||
|
name: "Charging"
|
||||||
|
full:
|
||||||
|
name: "Battery full"
|
||||||
|
charge_allowed:
|
||||||
|
name: "Charging enabled (hardware)"
|
||||||
|
level:
|
||||||
|
name: "IP5306 battery level"
|
||||||
|
current_limit:
|
||||||
|
name: "IP5306 charge current limit"
|
||||||
Reference in New Issue
Block a user