Skip to content

Commit

Permalink
Setting to disable formatting
Browse files Browse the repository at this point in the history
  • Loading branch information
maxwroc committed Dec 29, 2023
1 parent dd877e7 commit a841fae
Show file tree
Hide file tree
Showing 9 changed files with 100 additions and 11 deletions.
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ These options can be specified both per-entity and at the top level (affecting a
| unit | string | `"%"` | v2.1.0 | Override for unit displayed next to the state/level value ([example](#other-use-cases))
| value_override | [KString](#keyword-string-kstring) | | v3.0.0 | Allows to override the battery level value. Note: when used the `multiplier`, `round`, `state_map` setting is ignored
| non_battery_entity | boolean | `false` | v3.0.0 | Disables default battery state sources e.g. "battery_level" attribute
| default_state_formatting | bollean | `true` | v3.1.0 | Can be used to disable default state formatting e.g. entity display precission setting

### Keyword string (KString)

Expand Down
5 changes: 4 additions & 1 deletion src/battery-provider.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import { HomeAssistantExt } from "./type-extensions";
/**
* Properties which should be copied over to individual entities from the card
*/
const entititesGlobalProps: (keyof IBatteryEntityConfig)[] = [ "tap_action", "state_map", "charging_state", "secondary_info", "colors", "bulk_rename", "icon", "round", "unit", "value_override", "non_battery_entity" ];
const entititesGlobalProps: (keyof IBatteryEntityConfig)[] = [ "tap_action", "state_map", "charging_state", "secondary_info", "colors", "bulk_rename", "icon", "round", "unit", "value_override", "non_battery_entity", "default_state_formatting" ];

/**
* Class responsible for intializing Battery view models based on given configuration.
Expand All @@ -24,6 +24,9 @@ export class BatteryProvider {
*/
private exclude: Filter[] | undefined;

/**
* Collection of battery HTML elements.
*/
private batteries: IBatteryCollection = {};

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

this.name = getName(this.config, this.hass);
var { state, level} = getBatteryLevel(this.config, this.hass);
var { state, level, unit_override} = getBatteryLevel(this.config, this.hass);
this.state = state;

if (level !== undefined && this.config.unit !== "" && this.config.unit !== null) {
if (unit_override === undefined && level !== undefined && this.config.unit !== "" && this.config.unit !== null) {
this.unit = String.fromCharCode(160) + (this.config.unit || this.hass?.states[this.config.entity]?.attributes["unit_of_measurement"] || "%");
}
else {
this.unit = undefined;
this.unit = unit_override;
}

const isCharging = getChargingState(this.config, this.state, this.hass);
Expand Down
2 changes: 2 additions & 0 deletions src/custom-elements/battery-state-entity.views.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,8 @@ const replaceTags = (text: string, hass?: HomeAssistant): TemplateResult[] => {
result.push(html`${text.substring(currentPos, matchPos)}`);
}

console.log(matches);

result.push(html`<ha-relative-time .hass="${hass}" .datetime="${new Date(matches[1])}"></ha-relative-time>`);

currentPos += matchPos + matches[0].length;
Expand Down
18 changes: 15 additions & 3 deletions src/entity-fields/battery-level.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@ const stringValuePattern = /\b([0-9]{1,3})\s?%/;
export const getBatteryLevel = (config: IBatteryEntityConfig, hass?: HomeAssistantExt): IBatteryState => {
const UnknownLevel = hass?.localize("state.default.unknown") || "Unknown";
let state: string;
let unit: string | undefined;

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

Expand Down Expand Up @@ -94,13 +95,19 @@ export const getBatteryLevel = (config: IBatteryEntityConfig, hass?: HomeAssista
state = state.charAt(0).toUpperCase() + state.slice(1);
}

if (!displayValue && state === entityData.state) {
//displayValue = hass.formatEntityState(entityData);
// check if HA should format the value
if (config.default_state_formatting !== false && !displayValue && state === entityData.state) {
const formattedState = hass.formatEntityState(entityData);

// assuming it is a number followed by unit
[displayValue, unit] = formattedState.split(" ", 2);
unit = String.fromCharCode(160) + unit;
}

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

Expand All @@ -114,4 +121,9 @@ interface IBatteryState {
* Battery state to display
*/
state: string;

/**
* Unit override
*/
unit_override?: string
}
5 changes: 5 additions & 0 deletions src/typings.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -249,6 +249,11 @@ interface IBatteryEntityConfig {
* Whether the entity is not a battery entity
*/
non_battery_entity?: boolean;

/**
* Whether to allow HA to format the state value
*/
default_state_formatting?: boolean;
}

interface IBatteryCardConfig {
Expand Down
7 changes: 6 additions & 1 deletion test/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,8 @@ export class HomeAssistantMock<T extends LovelaceCard<any>> {

public hass: HomeAssistantExt = <any>{
states: {},
localize: jest.fn((key: string) => `[${key}]`)
localize: jest.fn((key: string) => `[${key}]`),
formatEntityState: jest.fn((entityData: any) => `${entityData.state} %`),
};

private throttledUpdate = throttledCall(() => {
Expand All @@ -85,6 +86,10 @@ export class HomeAssistantMock<T extends LovelaceCard<any>> {
}
}

mockFunc(funcName: keyof HomeAssistantExt, mockedFunc: Function) {
(<any>this.hass)[funcName] = jest.fn(<any>mockedFunc)
}

addCard<K extends LovelaceCard<T>>(type: string, config: extractGeneric<T>): T {
const elementName = type.replace("custom:", "");

Expand Down
20 changes: 20 additions & 0 deletions test/other/entity-fields/battery-level.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -181,4 +181,24 @@ describe("Battery level", () => {
expect(level).toBe(expectedLevel);
expect(state).toBe(expectedState);
});

test.each([
[undefined, "45", "dbm", { state: "[45]", level: 45, unit_override: String.fromCharCode(160) + "[dbm]" }], // test default when the setting is not set in the config
[true, "45", "dbm", { state: "[45]", level: 45, unit_override: String.fromCharCode(160) + "[dbm]" }], // test when the setting is explicitly true
[false, "45", "dbm", { state: "45", level: 45, unit_override: undefined }], // test when the setting is turned off
[true, "45", "dbm", { state: "56", level: 56, unit_override: undefined }, [ { from: "45", to: "56" } ]], // test when the state was changed by state_map
[true, "45", "dbm", { state: "33", level: 45, unit_override: undefined }, [ { from: "45", to: "45", display: "33" } ]], // test when the display value was changed by state_map
])
("default HA formatting ", (defaultStateFormatting: boolean | undefined, entityState: string, unitOfMeasurement: string, expected: { state: string, level: number, unit_override?: string }, stateMap: IConvert[] | undefined = undefined) => {

const hassMock = new HomeAssistantMock(true);
hassMock.addEntity("Mocked entity", entityState);
hassMock.mockFunc("formatEntityState", (entityData: any) => `[${entityData.state}] [${unitOfMeasurement}]`);

const { state, level, unit_override } = getBatteryLevel({ entity: "mocked_entity", default_state_formatting: defaultStateFormatting, state_map: stateMap }, hassMock.hass);

expect(level).toBe(expected.level);
expect(state).toBe(expected.state);
expect(unit_override).toBe(expected.unit_override);
});
});
47 changes: 44 additions & 3 deletions test/other/entity-fields/charging-state.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,15 @@ describe("Charging state", () => {
const hassMock = new HomeAssistantMock(true);
const isCharging = getChargingState({ entity: "any" }, "90", hassMock.hass);

expect(isCharging).toBe(false);
})

test("is false when there is no hass", () => {
const isCharging = getChargingState(
{ entity: "sensor.my_entity", charging_state: { attribute: [ { name: "is_charging", value: "true" } ] } },
"45",
undefined);

expect(isCharging).toBe(false);
})

Expand Down Expand Up @@ -44,15 +53,47 @@ describe("Charging state", () => {
expect(isCharging).toBe(true);
})

test("is true when charging state is in the external entity state", () => {
test("is false when charging state is in attribute (and attribute is missing)", () => {
const hassMock = new HomeAssistantMock(true);
const entity = hassMock.addEntity("Sensor", "80", { is_charging: "true" })
const entity = hassMock.addEntity("Sensor", "80")
const isCharging = getChargingState(
{ entity: entity.entity_id, charging_state: { attribute: [ { name: "status", value: "charging" }, { name: "is_charging", value: "true" } ] } },
entity.state,
hassMock.hass);

expect(isCharging).toBe(true);
expect(isCharging).toBe(false);
})

test.each([
["charging", true],
["charging", false, "MissingEntity"],
["discharging", false]
])("charging state is in the external entity state", (chargingEntityState: string, expected: boolean, missingEntitySuffix = "") => {
const hassMock = new HomeAssistantMock(true);
const entity = hassMock.addEntity("Sensor", "80")
const entityChargingState = hassMock.addEntity("Charging sensor", chargingEntityState)
const isCharging = getChargingState(
{ entity: entity.entity_id, charging_state: { entity_id: entityChargingState.entity_id + missingEntitySuffix, state: "charging" } },
entity.state,
hassMock.hass);

expect(isCharging).toBe(expected);
})

test.each([
["charging", true],
["full", true],
["full", false, " missing"],
])("default charging state", (chargingEntityState: string, expected: boolean, missingEntitySuffix = "") => {
const hassMock = new HomeAssistantMock(true);
const entity = hassMock.addEntity("Sensor battery level", "80", { is_charging: "true" })
const entityChargingState = hassMock.addEntity("Sensor battery state" + missingEntitySuffix, chargingEntityState)
const isCharging = getChargingState(
{ entity: entity.entity_id },
entity.state,
hassMock.hass);

expect(isCharging).toBe(expected);
})

});

0 comments on commit a841fae

Please sign in to comment.