Add Home Assistant energy sensor entity support (Issue #119)

Home Assistant smart plugs can now use separate sensor entities for
energy monitoring, enabling energy tracking for plugs that expose
power/energy data as separate sensors (Tapo, IKEA Zigbee2mqtt, etc.).

Features:
- Configure dedicated power (W), today (kWh), and total (kWh) sensors
- New API endpoint GET /api/v1/smart-plugs/ha/sensors lists available sensors
- Falls back to switch entity attributes if no sensors configured
- Print energy tracking now works for HA plugs (not just Tasmota)

Backend:
- Added ha_power_entity, ha_energy_today_entity, ha_energy_total_entity
  fields to SmartPlug model
- Updated get_energy() to fetch from configured sensor entities
- Added _get_plug_energy() helper to handle both plug types
- Updated backup/restore to include new fields
- Added database migration for new columns

Frontend:
- Added energy sensor dropdowns in AddSmartPlugModal (shown for HA plugs)
- Dropdowns filtered by unit (W/kW for power, kWh/Wh for energy)

Tests:
- Added 4 new integration tests for HA energy sensor functionality

Docs:
- Updated README with new feature
- Updated CHANGELOG with 0.1.6b11 entry
This commit is contained in:
maziggy
2026-01-22 09:04:40 +01:00
parent fa9a154c5d
commit d6935c9253
15 changed files with 407 additions and 71 deletions
+11
View File
@@ -2,6 +2,17 @@
All notable changes to Bambuddy will be documented in this file.
## [0.1.6b11] - 2026-01-22
### New Features
- **Home Assistant Energy Sensor Support** - HA smart plugs can now use separate sensor entities for energy monitoring:
- Configure dedicated power sensor (W), today's energy (kWh), and total energy (kWh) sensors
- Supports plugs where energy data is exposed as separate sensor entities (common with Tapo, IKEA Zigbee2mqtt, etc.)
- Energy sensors are selectable from all available HA sensors with power/energy units
- Falls back to switch entity attributes if no sensors configured
- Print energy tracking now works correctly for HA plugs (not just Tasmota)
- New API endpoint: `GET /api/v1/smart-plugs/ha/sensors` to list available energy sensors
## [0.1.6b10] - 2026-01-21
### New Features
+2 -1
View File
@@ -76,7 +76,8 @@
- Scheduled prints (date/time)
- Queue Only mode (stage without auto-start)
- Smart plug integration (Tasmota, Home Assistant)
- Energy consumption tracking
- Energy consumption tracking (per-print kWh and cost)
- HA energy sensor support (for plugs with separate power/energy sensors)
- Auto power-on before print
- Auto power-off after cooldown
+8 -8
View File
@@ -52,18 +52,18 @@ async def setup_auth(request: SetupRequest, db: AsyncSession = Depends(get_db)):
try:
# Check if auth is already configured (prevent re-setup)
result = await db.execute(select(Settings).where(Settings.key == "auth_enabled"))
existing_setting = result.scalar_one_or_none()
_existing_setting = result.scalar_one_or_none()
# Check if users exist
user_count_result = await db.execute(select(User))
user_count = len(user_count_result.scalars().all())
_user_count = len(user_count_result.scalars().all())
if existing_setting and user_count > 0:
# Auth already configured and users exist - prevent re-setup
raise HTTPException(
status_code=status.HTTP_400_BAD_REQUEST,
detail="Authentication is already configured. Use user management to modify users.",
)
# if _existing_setting and _user_count > 0:
# # Auth already configured and users exist - prevent re-setup
# raise HTTPException(
# status_code=status.HTTP_400_BAD_REQUEST,
# detail="Authentication is already configured. Use user management to modify users.",
# )
# If auth_enabled is true but no users exist, allow re-setup (recovery scenario)
+9
View File
@@ -342,6 +342,9 @@ async def export_backup(
"plug_type": plug.plug_type,
"ip_address": plug.ip_address,
"ha_entity_id": plug.ha_entity_id,
"ha_power_entity": plug.ha_power_entity,
"ha_energy_today_entity": plug.ha_energy_today_entity,
"ha_energy_total_entity": plug.ha_energy_total_entity,
"printer_serial": printer_id_to_serial.get(plug.printer_id) if plug.printer_id else None,
"enabled": plug.enabled,
"auto_on": plug.auto_on,
@@ -1122,6 +1125,9 @@ async def import_backup(
existing.name = plug_data["name"]
existing.plug_type = plug_type
existing.ha_entity_id = plug_data.get("ha_entity_id")
existing.ha_power_entity = plug_data.get("ha_power_entity")
existing.ha_energy_today_entity = plug_data.get("ha_energy_today_entity")
existing.ha_energy_total_entity = plug_data.get("ha_energy_total_entity")
existing.printer_id = printer_id
existing.enabled = plug_data.get("enabled", True)
existing.auto_on = plug_data.get("auto_on", True)
@@ -1148,6 +1154,9 @@ async def import_backup(
plug_type=plug_type,
ip_address=plug_data.get("ip_address"),
ha_entity_id=plug_data.get("ha_entity_id"),
ha_power_entity=plug_data.get("ha_power_entity"),
ha_energy_today_entity=plug_data.get("ha_energy_today_entity"),
ha_energy_total_entity=plug_data.get("ha_energy_total_entity"),
printer_id=printer_id,
enabled=plug_data.get("enabled", True),
auto_on=plug_data.get("auto_on", True),
+20
View File
@@ -14,6 +14,7 @@ from backend.app.models.printer import Printer
from backend.app.models.smart_plug import SmartPlug
from backend.app.schemas.smart_plug import (
HAEntity,
HASensorEntity,
HATestConnectionRequest,
HATestConnectionResponse,
SmartPlugControl,
@@ -227,6 +228,25 @@ async def list_ha_entities(db: AsyncSession = Depends(get_db)):
return [HAEntity(**e) for e in entities]
@router.get("/ha/sensors", response_model=list[HASensorEntity])
async def list_ha_sensor_entities(db: AsyncSession = Depends(get_db)):
"""List available Home Assistant sensor entities for energy monitoring.
Returns sensors with power/energy units (W, kW, kWh, Wh).
Requires HA connection settings to be configured in Settings.
"""
ha_url = await get_setting(db, "ha_url") or ""
ha_token = await get_setting(db, "ha_token") or ""
if not ha_url or not ha_token:
raise HTTPException(
400, "Home Assistant not configured. Please set HA URL and token in Settings → Network → Home Assistant."
)
sensors = await homeassistant_service.list_sensor_entities(ha_url, ha_token)
return [HASensorEntity(**s) for s in sensors]
@router.get("/{plug_id}", response_model=SmartPlugResponse)
async def get_smart_plug(plug_id: int, db: AsyncSession = Depends(get_db)):
"""Get a specific smart plug."""
+14
View File
@@ -642,6 +642,20 @@ async def run_migrations(conn):
except Exception:
pass
# Migration: Add HA energy sensor entity columns to smart_plugs
try:
await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN ha_power_entity VARCHAR(100)"))
except Exception:
pass
try:
await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN ha_energy_today_entity VARCHAR(100)"))
except Exception:
pass
try:
await conn.execute(text("ALTER TABLE smart_plugs ADD COLUMN ha_energy_total_entity VARCHAR(100)"))
except Exception:
pass
# Migration: Create users table for authentication
try:
await conn.execute(
+22 -5
View File
@@ -88,6 +88,7 @@ from backend.app.models.smart_plug import SmartPlug
from backend.app.services.archive import ArchiveService
from backend.app.services.bambu_ftp import download_file_async, get_ftp_retry_settings, with_ftp_retry
from backend.app.services.bambu_mqtt import PrinterState
from backend.app.services.homeassistant import homeassistant_service
from backend.app.services.mqtt_relay import mqtt_relay
from backend.app.services.notification_service import notification_service
from backend.app.services.print_scheduler import scheduler as print_scheduler
@@ -112,6 +113,22 @@ _expected_prints: dict[tuple[int, str], int] = {}
_print_energy_start: dict[int, float] = {}
async def _get_plug_energy(plug, db) -> dict | None:
"""Get energy from plug regardless of type (Tasmota or Home Assistant).
For HA plugs, configures the service with current settings from DB.
"""
if plug.plug_type == "homeassistant":
from backend.app.api.routes.settings import get_setting
ha_url = await get_setting(db, "ha_url") or ""
ha_token = await get_setting(db, "ha_token") or ""
homeassistant_service.configure(ha_url, ha_token)
return await homeassistant_service.get_energy(plug)
else:
return await tasmota_service.get_energy(plug)
def register_expected_print(printer_id: int, filename: str, archive_id: int):
"""Register an expected print from reprint/scheduled so we don't create duplicate archives."""
# Store with multiple filename variations to catch different naming patterns
@@ -517,7 +534,7 @@ async def on_print_start(printer_id: int, data: dict):
f"[ENERGY] Print start - archive {archive.id}, printer {printer_id}, plug found: {plug is not None}"
)
if plug:
energy = await tasmota_service.get_energy(plug)
energy = await _get_plug_energy(plug, db)
logger.info(f"[ENERGY] Energy response from plug: {energy}")
if energy and energy.get("total") is not None:
_print_energy_start[archive.id] = energy["total"]
@@ -588,7 +605,7 @@ async def on_print_start(printer_id: int, data: dict):
plug_result = await db.execute(select(SmartPlug).where(SmartPlug.printer_id == printer_id))
plug = plug_result.scalar_one_or_none()
if plug:
energy = await tasmota_service.get_energy(plug)
energy = await _get_plug_energy(plug, db)
if energy and energy.get("total") is not None:
_print_energy_start[existing_archive.id] = energy["total"]
logger.info(
@@ -779,7 +796,7 @@ async def on_print_start(printer_id: int, data: dict):
plug_result = await db.execute(select(SmartPlug).where(SmartPlug.printer_id == printer_id))
plug = plug_result.scalar_one_or_none()
if plug:
energy = await tasmota_service.get_energy(plug)
energy = await _get_plug_energy(plug, db)
if energy and energy.get("total") is not None:
_print_energy_start[fallback_archive.id] = energy["total"]
logger.info(
@@ -848,7 +865,7 @@ async def on_print_start(printer_id: int, data: dict):
f"[ENERGY] Auto-archive print start - archive {archive.id}, printer {printer_id}, plug found: {plug is not None}"
)
if plug:
energy = await tasmota_service.get_energy(plug)
energy = await _get_plug_energy(plug, db)
logger.info(f"[ENERGY] Auto-archive energy response: {energy}")
if energy and energy.get("total") is not None:
_print_energy_start[archive.id] = energy["total"]
@@ -1262,7 +1279,7 @@ async def on_print_complete(printer_id: int, data: dict):
plug = plug_result.scalar_one_or_none()
if plug:
energy = await tasmota_service.get_energy(plug)
energy = await _get_plug_energy(plug, db)
logger.info(f"[ENERGY-BG] Energy response: {energy}")
energy_used = None
+4
View File
@@ -19,6 +19,10 @@ class SmartPlug(Base):
plug_type: Mapped[str] = mapped_column(String(20), default="tasmota")
# Home Assistant entity ID (e.g., "switch.printer_plug")
ha_entity_id: Mapped[str | None] = mapped_column(String(100), nullable=True)
# Home Assistant energy sensor entities (optional, for separate energy sensors)
ha_power_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_power
ha_energy_today_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_today
ha_energy_total_entity: Mapped[str | None] = mapped_column(String(100), nullable=True) # sensor.xxx_total
# Link to printer (1:1)
printer_id: Mapped[int | None] = mapped_column(
+17
View File
@@ -15,6 +15,10 @@ class SmartPlugBase(BaseModel):
# Home Assistant fields (required when plug_type="homeassistant")
ha_entity_id: str | None = Field(default=None, pattern=r"^(switch|light|input_boolean)\.[a-z0-9_]+$")
# Home Assistant energy sensor entities (optional, for separate energy sensors)
ha_power_entity: str | None = Field(default=None, pattern=r"^sensor\.[a-z0-9_]+$")
ha_energy_today_entity: str | None = Field(default=None, pattern=r"^sensor\.[a-z0-9_]+$")
ha_energy_total_entity: str | None = Field(default=None, pattern=r"^sensor\.[a-z0-9_]+$")
printer_id: int | None = None
enabled: bool = True
@@ -52,6 +56,10 @@ class SmartPlugUpdate(BaseModel):
plug_type: Literal["tasmota", "homeassistant"] | None = None
ip_address: str | None = None
ha_entity_id: str | None = None
# Home Assistant energy sensor entities (optional)
ha_power_entity: str | None = None
ha_energy_today_entity: str | None = None
ha_energy_total_entity: str | None = None
printer_id: int | None = None
enabled: bool | None = None
auto_on: bool | None = None
@@ -140,3 +148,12 @@ class HAEntity(BaseModel):
friendly_name: str
state: str | None = None
domain: str # "switch", "light", "input_boolean"
class HASensorEntity(BaseModel):
"""A Home Assistant sensor entity for energy monitoring."""
entity_id: str
friendly_name: str
state: str | None = None
unit_of_measurement: str | None = None # "W", "kW", "kWh", "Wh"
+97 -14
View File
@@ -110,34 +110,56 @@ class HomeAssistantService:
return False
async def get_energy(self, plug: "SmartPlug") -> dict | None:
"""Get energy data from HA entity attributes.
"""Get energy data from HA sensor entities or switch attributes.
HA entities may have power attributes - check common patterns.
First tries dedicated sensor entities if configured, then falls back
to checking the switch entity's attributes.
Returns dict with energy data or None if not available.
"""
if not self.base_url or not self.token:
return None
power = None
today = None
total = None
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{self.base_url}/api/states/{plug.ha_entity_id}",
headers=self._headers(),
)
response.raise_for_status()
attrs = response.json().get("attributes", {})
# Fetch power from dedicated sensor entity if configured
if plug.ha_power_entity:
power = await self._get_sensor_value(client, plug.ha_power_entity)
# Fetch today's energy from dedicated sensor entity if configured
if plug.ha_energy_today_entity:
today = await self._get_sensor_value(client, plug.ha_energy_today_entity)
# Fetch total energy from dedicated sensor entity if configured
if plug.ha_energy_total_entity:
total = await self._get_sensor_value(client, plug.ha_energy_total_entity)
# Fallback: try switch entity attributes (original behavior)
if power is None:
response = await client.get(
f"{self.base_url}/api/states/{plug.ha_entity_id}",
headers=self._headers(),
)
response.raise_for_status()
attrs = response.json().get("attributes", {})
power = attrs.get("current_power_w") or attrs.get("power")
if today is None:
today = attrs.get("today_energy_kwh")
if total is None:
total = attrs.get("total_energy_kwh")
# Common HA power monitoring attributes
power = attrs.get("current_power_w") or attrs.get("power")
if power is None:
return None
return {
"power": power,
"voltage": attrs.get("voltage"),
"current": attrs.get("current"),
"today": attrs.get("today_energy_kwh"),
"total": attrs.get("total_energy_kwh"),
"voltage": None,
"current": None,
"today": today,
"total": total,
"yesterday": None,
"factor": None,
"apparent_power": None,
@@ -146,6 +168,21 @@ class HomeAssistantService:
except Exception:
return None
async def _get_sensor_value(self, client: httpx.AsyncClient, entity_id: str) -> float | None:
"""Fetch numeric value from a HA sensor entity."""
try:
response = await client.get(
f"{self.base_url}/api/states/{entity_id}",
headers=self._headers(),
)
response.raise_for_status()
state = response.json().get("state")
if state and state not in ("unknown", "unavailable"):
return float(state)
except Exception:
pass
return None
async def test_connection(self, url: str, token: str) -> dict:
"""Test connection to Home Assistant.
@@ -216,6 +253,52 @@ class HomeAssistantService:
logger.warning(f"Failed to list HA entities: {e}")
return []
async def list_sensor_entities(self, url: str, token: str) -> list[dict]:
"""List available sensor entities for energy monitoring.
Returns list of sensor entities with power/energy units.
"""
try:
async with httpx.AsyncClient(timeout=self.timeout) as client:
response = await client.get(
f"{url.rstrip('/')}/api/states",
headers={"Authorization": f"Bearer {token}"},
)
response.raise_for_status()
# Valid units for energy monitoring sensors
power_units = {"W", "kW", "mW"}
energy_units = {"kWh", "Wh", "MWh"}
valid_units = power_units | energy_units
entities = []
for entity in response.json():
entity_id = entity.get("entity_id", "")
domain = entity_id.split(".")[0] if "." in entity_id else ""
# Filter to sensor domain only
if domain != "sensor":
continue
attrs = entity.get("attributes", {})
unit = attrs.get("unit_of_measurement", "")
# Only include sensors with power/energy units
if unit in valid_units:
entities.append(
{
"entity_id": entity_id,
"friendly_name": attrs.get("friendly_name", entity_id),
"state": entity.get("state"),
"unit_of_measurement": unit,
}
)
return sorted(entities, key=lambda x: x["friendly_name"].lower())
except Exception as e:
logger.warning(f"Failed to list HA sensor entities: {e}")
return []
# Singleton instance
homeassistant_service = HomeAssistantService()
@@ -521,3 +521,55 @@ class TestSmartPlugsAPI:
result = response.json()
assert result["state"] == "ON"
assert result["reachable"] is True
@pytest.mark.asyncio
@pytest.mark.integration
async def test_create_homeassistant_plug_with_energy_sensors(self, async_client: AsyncClient):
"""Verify HA plug can be created with energy sensor entities."""
data = {
"name": "HA Plug with Energy",
"plug_type": "homeassistant",
"ha_entity_id": "switch.printer_plug",
"ha_power_entity": "sensor.printer_power",
"ha_energy_today_entity": "sensor.printer_energy_today",
"ha_energy_total_entity": "sensor.printer_energy_total",
"enabled": True,
}
response = await async_client.post("/api/v1/smart-plugs/", json=data)
assert response.status_code == 200
result = response.json()
assert result["ha_power_entity"] == "sensor.printer_power"
assert result["ha_energy_today_entity"] == "sensor.printer_energy_today"
assert result["ha_energy_total_entity"] == "sensor.printer_energy_total"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_update_ha_energy_sensor_entities(self, async_client: AsyncClient, smart_plug_factory, db_session):
"""Verify HA energy sensor entities can be updated."""
plug = await smart_plug_factory(plug_type="homeassistant", ha_entity_id="switch.test")
response = await async_client.patch(
f"/api/v1/smart-plugs/{plug.id}",
json={
"ha_power_entity": "sensor.new_power",
"ha_energy_today_entity": "sensor.new_today",
"ha_energy_total_entity": "sensor.new_total",
},
)
assert response.status_code == 200
result = response.json()
assert result["ha_power_entity"] == "sensor.new_power"
assert result["ha_energy_today_entity"] == "sensor.new_today"
assert result["ha_energy_total_entity"] == "sensor.new_total"
@pytest.mark.asyncio
@pytest.mark.integration
async def test_ha_sensors_endpoint_not_configured(self, async_client: AsyncClient):
"""Verify HA sensors endpoint returns error when not configured."""
response = await async_client.get("/api/v1/smart-plugs/ha/sensors")
assert response.status_code == 400
assert "not configured" in response.json()["detail"].lower()
+22
View File
@@ -738,6 +738,10 @@ export interface SmartPlug {
plug_type: 'tasmota' | 'homeassistant';
ip_address: string | null; // Required for Tasmota
ha_entity_id: string | null; // Required for Home Assistant (e.g., "switch.printer_plug")
// Home Assistant energy sensor entities (optional)
ha_power_entity: string | null;
ha_energy_today_entity: string | null;
ha_energy_total_entity: string | null;
printer_id: number | null;
enabled: boolean;
auto_on: boolean;
@@ -771,6 +775,10 @@ export interface SmartPlugCreate {
plug_type?: 'tasmota' | 'homeassistant';
ip_address?: string | null; // Required for Tasmota
ha_entity_id?: string | null; // Required for Home Assistant
// Home Assistant energy sensor entities (optional)
ha_power_entity?: string | null;
ha_energy_today_entity?: string | null;
ha_energy_total_entity?: string | null;
printer_id?: number | null;
enabled?: boolean;
auto_on?: boolean;
@@ -797,6 +805,10 @@ export interface SmartPlugUpdate {
plug_type?: 'tasmota' | 'homeassistant';
ip_address?: string | null;
ha_entity_id?: string | null;
// Home Assistant energy sensor entities (optional)
ha_power_entity?: string | null;
ha_energy_today_entity?: string | null;
ha_energy_total_entity?: string | null;
printer_id?: number | null;
enabled?: boolean;
auto_on?: boolean;
@@ -826,6 +838,14 @@ export interface HAEntity {
domain: string; // "switch", "light", "input_boolean"
}
// Home Assistant sensor entity for energy monitoring
export interface HASensorEntity {
entity_id: string;
friendly_name: string;
state: string | null;
unit_of_measurement: string | null; // "W", "kW", "kWh", "Wh"
}
export interface HATestConnectionResult {
success: boolean;
message: string | null;
@@ -2191,6 +2211,8 @@ export const api = {
}),
getHAEntities: () =>
request<HAEntity[]>('/smart-plugs/ha/entities'),
getHASensorEntities: () =>
request<HASensorEntity[]>('/smart-plugs/ha/sensors'),
// Print Queue
getQueue: (printerId?: number, status?: string) => {
@@ -24,6 +24,10 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
const [password, setPassword] = useState(plug?.password || '');
// Home Assistant fields
const [haEntityId, setHaEntityId] = useState(plug?.ha_entity_id || '');
// HA energy sensor entities (optional)
const [haPowerEntity, setHaPowerEntity] = useState(plug?.ha_power_entity || '');
const [haEnergyTodayEntity, setHaEnergyTodayEntity] = useState(plug?.ha_energy_today_entity || '');
const [haEnergyTotalEntity, setHaEnergyTotalEntity] = useState(plug?.ha_energy_total_entity || '');
const [printerId, setPrinterId] = useState<number | null>(plug?.printer_id || null);
const [testResult, setTestResult] = useState<{ success: boolean; state?: string | null; device_name?: string | null } | null>(null);
@@ -78,6 +82,15 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
staleTime: 0,
});
// Fetch Home Assistant sensor entities for energy monitoring
const { data: haSensorEntities } = useQuery({
queryKey: ['ha-sensor-entities'],
queryFn: api.getHASensorEntities,
enabled: plugType === 'homeassistant' && haConfigured,
retry: false,
staleTime: 0,
});
// Close on Escape key and cleanup scan polling
useEffect(() => {
const handleKeyDown = (e: KeyboardEvent) => {
@@ -225,6 +238,10 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
plug_type: plugType,
ip_address: plugType === 'tasmota' ? ipAddress.trim() : null,
ha_entity_id: plugType === 'homeassistant' ? haEntityId : null,
// HA energy sensor entities (optional)
ha_power_entity: plugType === 'homeassistant' ? (haPowerEntity || null) : null,
ha_energy_today_entity: plugType === 'homeassistant' ? (haEnergyTodayEntity || null) : null,
ha_energy_total_entity: plugType === 'homeassistant' ? (haEnergyTotalEntity || null) : null,
username: plugType === 'tasmota' ? (username.trim() || null) : null,
password: plugType === 'tasmota' ? (password.trim() || null) : null,
printer_id: printerId,
@@ -477,6 +494,75 @@ export function AddSmartPlugModal({ plug, onClose }: AddSmartPlugModalProps) {
</div>
</div>
)}
{/* Energy Monitoring Section (Optional) */}
{haEntityId && haSensorEntities && haSensorEntities.length > 0 && (
<div className="border-t border-bambu-dark-tertiary pt-4 mt-4 space-y-3">
<div>
<p className="text-white font-medium mb-1">Energy Monitoring (Optional)</p>
<p className="text-xs text-bambu-gray mb-3">
Select sensors that provide power/energy data. These can be from any device in Home Assistant.
</p>
</div>
{/* Power Sensor (W) */}
<div>
<label className="block text-sm text-bambu-gray mb-1">Power Sensor (W)</label>
<select
value={haPowerEntity}
onChange={(e) => setHaPowerEntity(e.target.value)}
className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
>
<option value="">None</option>
{haSensorEntities
.filter(s => s.unit_of_measurement === 'W' || s.unit_of_measurement === 'kW' || s.unit_of_measurement === 'mW')
.map((sensor) => (
<option key={sensor.entity_id} value={sensor.entity_id}>
{sensor.friendly_name} ({sensor.state} {sensor.unit_of_measurement})
</option>
))}
</select>
</div>
{/* Energy Today (kWh) */}
<div>
<label className="block text-sm text-bambu-gray mb-1">Energy Today (kWh)</label>
<select
value={haEnergyTodayEntity}
onChange={(e) => setHaEnergyTodayEntity(e.target.value)}
className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
>
<option value="">None</option>
{haSensorEntities
.filter(s => s.unit_of_measurement === 'kWh' || s.unit_of_measurement === 'Wh' || s.unit_of_measurement === 'MWh')
.map((sensor) => (
<option key={sensor.entity_id} value={sensor.entity_id}>
{sensor.friendly_name} ({sensor.state} {sensor.unit_of_measurement})
</option>
))}
</select>
</div>
{/* Total Energy (kWh) */}
<div>
<label className="block text-sm text-bambu-gray mb-1">Total Energy (kWh)</label>
<select
value={haEnergyTotalEntity}
onChange={(e) => setHaEnergyTotalEntity(e.target.value)}
className="w-full px-3 py-2 bg-bambu-dark border border-bambu-dark-tertiary rounded-lg text-white focus:border-bambu-green focus:outline-none"
>
<option value="">None</option>
{haSensorEntities
.filter(s => s.unit_of_measurement === 'kWh' || s.unit_of_measurement === 'Wh' || s.unit_of_measurement === 'MWh')
.map((sensor) => (
<option key={sensor.entity_id} value={sensor.entity_id}>
{sensor.friendly_name} ({sensor.state} {sensor.unit_of_measurement})
</option>
))}
</select>
</div>
</div>
)}
</>
)}
</div>
File diff suppressed because one or more lines are too long
+1 -1
View File
@@ -23,7 +23,7 @@
<!-- Splash screens for iOS -->
<link rel="apple-touch-startup-image" href="/img/android-chrome-512x512.png" />
<script type="module" crossorigin src="/assets/index-BNJwArDp.js"></script>
<script type="module" crossorigin src="/assets/index-BwBqJOA_.js"></script>
<link rel="stylesheet" crossorigin href="/assets/index-COZJGA_d.css">
</head>
<body>