Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ This card is designed for the new [Home Assistant Sections UI](https://www.home-
| Curved Line Radius | `curve_factor`| Adjusts the curve radius. `0` to `5`, `1` is default. Set to `0` for straight lines. |
| Font Size Multiplier | `font_size_multiplier`| Multiplies text sizes on top of the automatic `--cpc-scale`. Default `1.0`. |
| Device Power Lines | `show_device_power_lines`| Set to `true` to light up devices when power is flowing beyond a threshold. Default `false` |
| Remaining Device | `show_remaining_device` | Set to `true` to add a virtual device showing home power not accounted for by configured devices. Default `false`. |
| Remaining Device Name | `remaining_device_name` | Display name for the remaining device. Default: `Overige`. |
| Remaining Device Icon | `remaining_device_icon` | MDI icon for the remaining device. Default: `mdi:devices`. |
| Remaining Device Color | `remaining_device_color` | Color for the remaining device icon and label. Default: `#9e9e9e`. |
| Remaining Device Threshold | `remaining_device_threshold` | Hides the remaining device when below this threshold in watts. Default: `5`. |
| Home Icon Gradient | `disable_home_gradient`| Set to `true` if you want the home icon to be a single colour. |
| Remove Glow Effects | `remove_glow_effects`| Set to `true` to disable drop shadow/glow effects. Default `false` (dark mode only). |
| Hide Card Background | `hide_card_background`| Set to `true` to hide the standard HA card background. Default `false`. |
Expand Down Expand Up @@ -241,6 +246,34 @@ Example below of how devices can be setup:
threshold: 50
```

## Remaining Device

The remaining device is an optional virtual device that displays the portion of home power consumption not accounted for by configured devices.

Enable it with `show_remaining_device: true` at the card level. The value is calculated as:

`home power − sum of configured device power`

The result is clamped to zero and exists only inside the card — no Home Assistant template sensor is required. When tapped, the remaining device opens the more-info panel of the home entity.

| Name | Setting slug | Default |
| ---- | ------------ | ------- |
| Enable | `show_remaining_device` | `false` |
| Name | `remaining_device_name` | `Overige` |
| Icon | `remaining_device_icon` | `mdi:devices` |
| Color | `remaining_device_color` | `#9e9e9e` |
| Threshold | `remaining_device_threshold` | `5` |

Example:

```yaml
show_remaining_device: true
remaining_device_name: Other
remaining_device_icon: mdi:help-circle
remaining_device_color: "#9e9e9e"
remaining_device_threshold: 10
```

## Labels (per pv/grid/battery)

Labels can be used to display "other" information - that can be more power stats, energy stats, weather, whatever you want. Note: these are just labels, they do not factor into the power diagram or calculations at all.
Expand Down
93 changes: 82 additions & 11 deletions compact-power-card.js
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,27 @@ class CompactPowerCard extends CompactPowerCardBase {
{
name: "enable_device_power_lines",
selector: { boolean: { } },
},
},
{
name: "show_remaining_device",
selector: { boolean: {} },
},
{
name: "remaining_device_name",
selector: { text: {} },
},
{
name: "remaining_device_icon",
selector: { icon: {} },
},
{
name: "remaining_device_color",
selector: { text: {} },
},
{
name: "remaining_device_threshold",
selector: { number: { step: 1 } },
},
{
name: "disable_home_gradient",
selector: { boolean: {} },
Expand Down Expand Up @@ -3127,7 +3147,7 @@ class CompactPowerCard extends CompactPowerCardBase {
? this._config?.entities?.battery_labels || this._config?.entities?.battery?.labels
: batteryCfg?.labels;
const batteryLabels = this._normalizeLabels(batteryLabelsSource, null);
const { sources: normalizedSources } = this._getSourcesConfig();
let { sources: normalizedSources } = this._getSourcesConfig();
const enableDevicePowerLines = this._useDevicePowerLines();
const allowGlow = this._allowGlowEffects();
const homeGlowOpacity = allowGlow ? 0.3 : 0;
Expand Down Expand Up @@ -3369,6 +3389,38 @@ class CompactPowerCard extends CompactPowerCardBase {
pvUnitRaw || gridUnitRaw || batteryUnitRaw || homeUnitOverride || "W";
const homeUnit = "W";
const homeNumericW = this._toWatts(homeNumeric, inferredHomeUnit);

// Optional virtual device: all household usage not covered by configured devices.
if (this._coerceBoolean(this._config?.show_remaining_device, false)) {
const knownDeviceWatts = normalizedSources.reduce((sum, src) => {
const entity = src?.entity || null;
if (!this._isPowerDevice(entity)) return sum;
const attribute = src?.attribute || null;
const unit = this.hass?.states?.[entity]?.attributes?.unit_of_measurement || "";
const meta = this._getPowerMeta(entity, unit, attribute);
const watts = Number.isFinite(meta?.watts) ? Math.max(meta.watts, 0) : 0;
return sum + watts;
}, 0);

const remainingWatts = Math.max(
(Number.isFinite(homeNumericW) ? homeNumericW : 0) - knownDeviceWatts,
0
);

normalizedSources = [
...normalizedSources,
{
entity: "__cpc_remaining_devices__",
virtual: true,
virtual_watts: remainingWatts,
name: this._config?.remaining_device_name || "Overige",
icon: this._config?.remaining_device_icon || "mdi:devices",
color: this._config?.remaining_device_color || "#9e9e9e",
threshold: this._config?.remaining_device_threshold ?? 5,
subtract_from_home: false,
},
];
}
const homeEffectiveW = this._homeEffective || 0;
const homeEffectiveRender = homeEffectiveW;
const homeThresholdDisplay = this._toWatts(this._parseThreshold(homeCfg.threshold), "W", true);
Expand Down Expand Up @@ -3534,13 +3586,22 @@ class CompactPowerCard extends CompactPowerCardBase {
const name = src.name || null;
const nameEntity = name && this.hass?.states?.[name] ? name : null;
const displayName = nameEntity ? this._formatEntityStateWithUnit(nameEntity) : name;
const icon = src.icon || this._getEntityIcon(entity, "mdi:power-plug");
const isPowerDevice = this._isPowerDevice(entity);
const st = entity ? this.hass?.states?.[entity] : null;
const raw = attribute ? st?.attributes?.[attribute] : st?.state;
const isUnavailable = this._isUnavailableState(raw);
const numeric = isUnavailable ? 0 : this._getNumericMaybe(entity, attribute);
const unit = st?.attributes?.unit_of_measurement || "";
const isVirtual = this._coerceBoolean(src.virtual, false);
const icon = src.icon || (isVirtual ? "mdi:devices" : this._getEntityIcon(entity, "mdi:power-plug"));
const isPowerDevice = isVirtual || this._isPowerDevice(entity);
const st = !isVirtual && entity ? this.hass?.states?.[entity] : null;
const raw = isVirtual
? src.virtual_watts
: attribute
? st?.attributes?.[attribute]
: st?.state;
const isUnavailable = isVirtual ? false : this._isUnavailableState(raw);
const numeric = isVirtual
? Number(src.virtual_watts) || 0
: isUnavailable
? 0
: this._getNumericMaybe(entity, attribute);
const unit = isVirtual ? "W" : st?.attributes?.unit_of_measurement || "";
const decimals = this._getDecimalPlaces(src);
const numericW = isPowerDevice
? isUnavailable
Expand Down Expand Up @@ -3581,6 +3642,7 @@ class CompactPowerCard extends CompactPowerCardBase {
hidden,
numeric: hasPowerNumeric ? numericW : numeric ?? 0,
isPowerDevice,
isVirtual,
threshold,
forceHideUnderThreshold,
};
Expand All @@ -3589,7 +3651,12 @@ class CompactPowerCard extends CompactPowerCardBase {
const visibleSources = deviceSources.filter(
(src) => !(src.forceHideUnderThreshold && src.hidden)
);
const maxDevices = Math.min(visibleSources.length, maxItemsByColumns);
// Show all configured devices, including the virtual remaining-usage device.
// The previous column-based cap could silently remove the last item (usually "Overige").
const configuredMaxDevices = this._parseNumberStrict(this._config?.max_device_items);
const maxDevices = Number.isFinite(configuredMaxDevices)
? Math.max(1, Math.min(visibleSources.length, Math.floor(configuredMaxDevices)))
: visibleSources.length;
const deviceVisible = visibleSources.slice(0, maxDevices);

if (maxDevices > 0) {
Expand Down Expand Up @@ -3628,7 +3695,7 @@ class CompactPowerCard extends CompactPowerCardBase {

const sources = deviceVisible.map((src, idx) => {
const pos = sourcePositions[idx] || { x: homeX, y: homeRowYBase };
const key = src.entity || `idx-${src.sourceIndex}`;
const key = src.isVirtual ? "__cpc_remaining_devices__" : src.entity || `idx-${src.sourceIndex}`;
let active = false;
let flicker = false;
let flickerUntil = 0;
Expand Down Expand Up @@ -4270,6 +4337,8 @@ class CompactPowerCard extends CompactPowerCardBase {
@click=${() => {
if (src.switchEntity) {
this._toggleDeviceSwitch(src);
} else if (src.isVirtual) {
this._openMoreInfo(homeCfg.entity || null);
} else {
this._openMoreInfo(src.entity || null);
}
Expand All @@ -4286,6 +4355,8 @@ class CompactPowerCard extends CompactPowerCardBase {
@click=${() => {
if (src.switchEntity) {
this._toggleDeviceSwitch(src);
} else if (src.isVirtual) {
this._openMoreInfo(homeCfg.entity || null);
} else {
this._openMoreInfo(src.entity || null);
}
Expand Down