Skip to content

Commit

Permalink
Merge pull request #588 from maxwroc/StateMapDisplay
Browse files Browse the repository at this point in the history
State-map 'display' property allowing to override displayed state
  • Loading branch information
maxwroc authored Oct 24, 2023
2 parents 57f33fe + 2bf5894 commit 8ca9525
Show file tree
Hide file tree
Showing 9 changed files with 135 additions and 79 deletions.
14 changes: 10 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -60,6 +60,7 @@ colors:
```
### Card config
| Name | Type | Default | Since | Description |
|:-----|:-----|:-----|:-----|:-----|
| type | string | **(required)** | v0.9.0 | Must be `custom:battery-state-entity` |
Expand All @@ -73,6 +74,7 @@ colors:
+[common options](#common-options) (if specified they will be apllied to all entities)

### Entity object

| Name | Type | Default | Since | Description |
|:-----|:-----|:-----|:-----|:-----|
| type | string | | v0.9.0 | Must be `custom:battery-state-entity` if used as entity row e.g. in entity-list card |
Expand All @@ -85,6 +87,7 @@ colors:
+[common options](#common-options) (if specified they will override the card-level ones)

### Common options

These options can be specified both per-entity and at the top level (affecting all entities).

| Name | Type | Default | Since | Description |
Expand Down Expand Up @@ -199,6 +202,7 @@ Operator is an optional property. If operator is not specified it depends on `va
| `"matches"` | If value matches the one specified in `value` property. You can use wildcards (e.g. `"*_battery_level"`) or regular expression (must be prefixed and followed by slash e.g. `"/[a-z_]+_battery_level/"`)

### Tap-Action

The definition is similar to the default [tap-action](https://www.home-assistant.io/lovelace/actions/#tap-action) in HomeAssistant.
| Name | Type | Default | Description |
|:-----|:-----|:-----|:-----|
Expand All @@ -210,10 +214,11 @@ The definition is similar to the default [tap-action](https://www.home-assistant

### Convert

| Name | Type | Default | Description |
|:-----|:-----|:-----|:-----|
| from | any | **(required)** | Value to convert. Note it is type sensitive (eg. `false` != `"false"`)
| to | any | **(required)** | Target value
| Name | Type | Default | Since | Description |
|:-----|:-----|:-----|:-----|:-----|
| from | any | **(required)** | v1.1.0 | Value to convert. Note it is type sensitive (eg. `false` != `"false"`)
| to | any | **(required)** | v1.1.0 | Target value
| display | string | | v3.0.0 | Override for displayed entity state (when the current entiy state matches the `from` value)

### Charging-state object

Expand Down Expand Up @@ -245,6 +250,7 @@ Note: All of these values are optional but at least `entity_id` or `state` or `a
| min | number | | v1.4.0 | Minimal battery level. Batteries below that level won't be assigned to this group.
| max | number | | v1.4.0 | Maximal battery level. Batteries above that level won't be assigned to this group.
| entities | list(string) | | v1.4.0 | List of endity ids

## Examples

You can use this component as a card or as an entity (e.g. in `entities card`);
Expand Down
10 changes: 4 additions & 6 deletions src/colors.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,22 +7,20 @@ import { log, safeGetConfigArrayOfObjects } from "./utils";
* @param isCharging Whether battery is in chargin mode
* @returns Icon color
*/
export const getColorForBatteryLevel = (config: IBatteryEntityConfig, batteryLevel: string, isCharging: boolean): string => {

const level = Number(batteryLevel);
export const getColorForBatteryLevel = (config: IBatteryEntityConfig, batteryLevel: number | undefined, isCharging: boolean): string => {

if (isCharging && config.charging_state?.color) {
return config.charging_state.color;
}

if (isNaN(level) || level > 100 || level < 0) {
if (batteryLevel === undefined || isNaN(batteryLevel) || batteryLevel > 100 || batteryLevel < 0) {
return defaultColor;
}

const colorSteps = safeGetConfigArrayOfObjects(config.colors?.steps, "color");

if (config.colors?.gradient) {
return getGradientColors(colorSteps, level);
return getGradientColors(colorSteps, batteryLevel);
}

let thresholds: IColorSteps[] = defaultColorSteps;
Expand All @@ -34,7 +32,7 @@ import { log, safeGetConfigArrayOfObjects } from "./utils";
});
}

return thresholds.find(th => level <= th.value!)?.color || defaultColor;
return thresholds.find(th => batteryLevel <= th.value!)?.color || defaultColor;
}

/**
Expand Down
10 changes: 6 additions & 4 deletions src/custom-elements/battery-state-entity.ts
Original file line number Diff line number Diff line change
Expand Up @@ -69,8 +69,10 @@ export class BatteryStateEntity extends LovelaceCard<IBatteryEntityConfig> {

async internalUpdate() {
this.name = getName(this.config, this.hass);
this.state = getBatteryLevel(this.config, this.hass);
if (isNumber(this.state)) {
var { state, level} = getBatteryLevel(this.config, this.hass);
this.state = state;

if (level !== undefined) {
this.unit = String.fromCharCode(160) + (this.config.unit || this.hass?.states[this.config.entity]?.attributes["unit_of_measurement"] || "%");
}
else {
Expand All @@ -79,8 +81,8 @@ export class BatteryStateEntity extends LovelaceCard<IBatteryEntityConfig> {

const isCharging = getChargingState(this.config, this.state, this.hass);
this.secondaryInfo = getSecondaryInfo(this.config, this.hass, isCharging);
this.icon = getIcon(this.config, Number(this.state), isCharging, this.hass);
this.iconColor = getColorForBatteryLevel(this.config, this.state, isCharging);
this.icon = getIcon(this.config, level, isCharging, this.hass);
this.iconColor = getColorForBatteryLevel(this.config, level, isCharging);
}

connectedCallback() {
Expand Down
69 changes: 48 additions & 21 deletions src/entity-fields/battery-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,26 +13,33 @@ import { isNumber, log } from "../utils";
* @param hass HomeAssistant state object
* @returns Battery level
*/
export const getBatteryLevel = (config: IBatteryEntityConfig, hass?: HomeAssistant): string => {
export const getBatteryLevel = (config: IBatteryEntityConfig, hass?: HomeAssistant): IBatteryState => {
const UnknownLevel = hass?.localize("state.default.unknown") || "Unknown";
let level: string;
let state: string;

const stringProcessor = new RichStringProcessor(hass, config.entity);

if (config.value_override !== undefined) {
const proc = new RichStringProcessor(hass, config.entity);
return proc.process(config.value_override.toString());
const processedValue = stringProcessor.process(config.value_override.toString());
return {
state: processedValue,
level: isNumber(processedValue) ? Number(processedValue) : undefined
}
}

const entityData = hass?.states[config.entity];

if (!entityData) {
return UnknownLevel;
return {
state: UnknownLevel
};
}

if (config.attribute) {
level = entityData.attributes[config.attribute];
if (level == undefined) {
state = entityData.attributes[config.attribute];
if (state == undefined) {
log(`Attribute "${config.attribute}" doesn't exist on "${config.entity}" entity`);
level = UnknownLevel;
state = UnknownLevel;
}
}
else {
Expand All @@ -42,45 +49,65 @@ import { isNumber, log } from "../utils";
entityData.state
];

level = candidates.find(val => isNumber(val)) ||
state = candidates.find(val => isNumber(val)) ||
candidates.find(val => val !== null && val !== undefined)?.toString() ||
UnknownLevel
}

let displayValue: string | undefined;

// check if we should convert value eg. for binary sensors
if (config.state_map) {
const convertedVal = config.state_map.find(s => s.from === level);
const convertedVal = config.state_map.find(s => s.from === state);
if (convertedVal === undefined) {
if (!isNumber(level)) {
log(`Missing option for '${level}' in 'state_map.'`);
if (!isNumber(state)) {
log(`Missing option for '${state}' in 'state_map.'`);
}
}
else {
level = convertedVal.to.toString();
state = convertedVal.to.toString();
if (convertedVal.display !== undefined) {
displayValue = stringProcessor.process(convertedVal.display);
}
}
}

// trying to extract value from string e.g. "34 %"
if (!isNumber(level)) {
const match = stringValuePattern.exec(level);
if (!isNumber(state)) {
const match = stringValuePattern.exec(state);
if (match != null) {
level = match[1];
state = match[1];
}
}

if (isNumber(level)) {
if (isNumber(state)) {
if (config.multiplier) {
level = (config.multiplier * Number(level)).toString();
state = (config.multiplier * Number(state)).toString();
}

if (typeof config.round === "number") {
level = parseFloat(level).toFixed(config.round).toString();
state = parseFloat(state).toFixed(config.round).toString();
}
}
else {
// capitalize first letter
level = level.charAt(0).toUpperCase() + level.slice(1);
state = state.charAt(0).toUpperCase() + state.slice(1);
}

return level;
return {
state: displayValue || state,
level: isNumber(state) ? Number(state) : undefined
};
}

interface IBatteryState {
/**
* Battery level
*/
level?: number;

/**
* Battery state to display
*/
state: string;
}
4 changes: 2 additions & 2 deletions src/entity-fields/get-icon.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import { RichStringProcessor } from "../rich-string-processor";
* @param hass HomeAssistant state object
* @returns Mdi icon string
*/
export const getIcon = (config: IBatteryEntityConfig, level: number, isCharging: boolean, hass: HomeAssistant | undefined): string => {
export const getIcon = (config: IBatteryEntityConfig, level: number | undefined, isCharging: boolean, hass: HomeAssistant | undefined): string => {
if (isCharging && config.charging_state?.icon) {
return config.charging_state.icon;
}
Expand All @@ -33,7 +33,7 @@ export const getIcon = (config: IBatteryEntityConfig, level: number, isCharging:
return processor.process(config.icon);
}

if (isNaN(level) || level > 100 || level < 0) {
if (level === undefined || isNaN(level) || level > 100 || level < 0) {
return "mdi:battery-unknown";
}

Expand Down
5 changes: 5 additions & 0 deletions src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,11 @@ interface IConvert {
* Value replacement
*/
to: string;

/**
* Entity state to display
*/
display?: string;
}

/**
Expand Down
2 changes: 1 addition & 1 deletion src/utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ export const log = (message: string, level: "warn" | "error" = "warn") => {
*/
export const isNumber = (value: string | number): boolean =>
{
return (value !== null && value !== '' && !isNaN(Number(value)))
return (value!== undefined && value !== null && value !== '' && !isNaN(Number(value)))
}
/**
* Returns array of values regardles if given value is string array or null
Expand Down
10 changes: 5 additions & 5 deletions test/other/colors.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ describe("Colors", () => {
[56, "var(--label-badge-green)"],
[100, "var(--label-badge-green)"],
])("default steps", (batteryLevel: number, expectedColor: string) => {
const result = getColorForBatteryLevel({ entity: "", colors: undefined }, batteryLevel.toString(), false);
const result = getColorForBatteryLevel({ entity: "", colors: undefined }, batteryLevel, false);

expect(result).toBe(expectedColor);
})
Expand All @@ -32,7 +32,7 @@ describe("Colors", () => {
{ value: 100, color: "green" }
]
}
const result = getColorForBatteryLevel({ entity: "", colors: colorsConfig }, batteryLevel.toString(), false);
const result = getColorForBatteryLevel({ entity: "", colors: colorsConfig }, batteryLevel, false);

expect(result).toBe(expectedColor);
})
Expand All @@ -44,7 +44,7 @@ describe("Colors", () => {
[75, "#7fff00"],
[100, "#00ff00"],
])("gradient simple color list", (batteryLevel: number, expectedColor: string) => {
const result = getColorForBatteryLevel({ entity: "", colors: { steps: ["#ff0000", "#ffff00", "#00ff00"], gradient: true } }, batteryLevel.toString(), false);
const result = getColorForBatteryLevel({ entity: "", colors: { steps: ["#ff0000", "#ffff00", "#00ff00"], gradient: true } }, batteryLevel, false);

expect(result).toBe(expectedColor);
})
Expand Down Expand Up @@ -72,7 +72,7 @@ describe("Colors", () => {
}
}

const result = getColorForBatteryLevel(config, batteryLevel.toString(), false);
const result = getColorForBatteryLevel(config, batteryLevel, false);

expect(result).toBe(expectedColor);
})
Expand All @@ -85,7 +85,7 @@ describe("Colors", () => {
}
}

const result = getColorForBatteryLevel(config, "80", false);
const result = getColorForBatteryLevel(config, 80, false);

expect(result).toBe("inherit");
})
Expand Down
Loading

0 comments on commit 8ca9525

Please sign in to comment.