Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Show Rain Delay Stop Time if rain delay is active #17

Merged
merged 3 commits into from
Jul 9, 2022
Merged
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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ Otherwise, make sure:
- The ids of program running binary sensors end with `_running`
- The id of the OpenSprinkler controller enabled switch ends with `_enabled`
- The ids of program & station enabled switches end with `_enabled`
- The id of the rain delay active binary sensor ends with `_rain_delay_active`
- The id of the rain delay stop time sensor ends with `_rain_delay_stop_time`

## Extra entities and duration control

Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@
"repository": "[email protected]:rianadon/opensprinkler-card.git",
"license": "Apache-2.0",
"dependencies": {
"@formatjs/intl-utils": "^3.8.4",
"@mdi/js": "^6.5.95",
"custom-card-helpers": "^1.8.0",
"custom-card-helpers": "1.8.0",
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Note: This is explicitly removing the "compatible with" ^ qualifier, in favor of pinning the custom-card-helpers version at exactly 1.8.0. Version 1.9.0 ought to be compatible, but took a major version bump of home-assistant-js-websocket, and we're exposed to the breaking changes due to data type incompatibility.

"home-assistant-js-websocket": "^5.12.0",
"lit": "^2.0.2",
"lovelace-timer-bar-card": "^1.15.0"
Expand Down
9 changes: 9 additions & 0 deletions src/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -25,13 +25,22 @@ export const isStationProgEnable = (entity: HassEntity) =>
entity.entity_id.startsWith('switch.');
export const isPlayPausable = (entity: HassEntity) =>
isStation(entity) || isProgram(entity) || isRunOnce(entity)
export const isRainDelayActiveSensor = (entity: HassEntity) =>
entity.entity_id.startsWith('binary_sensor.') &&
entity.entity_id.endsWith('rain_delay_active')
export const isRainDelayStopTime = (entity: HassEntity) =>
entity.entity_id.startsWith('sensor.') &&
entity.entity_id.endsWith('rain_delay_stop_time')

export function hasRunOnce(entities: EntitiesFunc) {
return entities(isStation).some(e => e.attributes.running_program_id === RUN_ONCE_ID);
}
export function hasManual(entities: EntitiesFunc) {
return entities(isStation).some(e => e.attributes.running_program_id === MANUAL_ID);
}
export function hasRainDelayActive(entities: EntitiesFunc) {
return entities(isRainDelayActiveSensor).some(e => e.state === 'on');
rianadon marked this conversation as resolved.
Show resolved Hide resolved
}

export const stateWaiting = (entity: HassEntity) => WAITING_STATES.includes(entity.state);
export const stateStoppable = (entity: HassEntity) => STOPPABLE_STATES.includes(entity.state);
Expand Down
8 changes: 7 additions & 1 deletion src/opensprinkler-card.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,9 +13,10 @@ import "./opensprinkler-generic-entity-row";
import "./opensprinkler-more-info-dialog";
import "./opensprinkler-control";
import { MoreInfoDialog } from './opensprinkler-more-info-dialog';
import { EntitiesFunc, hasManual, hasRunOnce, isPlayPausable, isProgram, isStation, lineHeight, osName, stateActivated, stateWaiting } from './helpers';
import { EntitiesFunc, hasManual, hasRainDelayActive, hasRunOnce, isPlayPausable, isProgram, isRainDelayStopTime, isStation, lineHeight, osName, stateActivated, stateWaiting } from './helpers';
import { renderState } from './opensprinkler-state';
import { styleMap } from 'lit/directives/style-map';
import { relativeTime } from './relative_time';

// This puts your card into the UI card picker dialog
(window as any).customCards = (window as any).customCards || [];
Expand Down Expand Up @@ -209,6 +210,11 @@ export class OpensprinklerCard extends LitElement {
private _secondaryText() {
const entities: EntitiesFunc = p => this._matchingEntities(p)

if (hasRainDelayActive(entities)) {
const stop_time = entities(isRainDelayStopTime).find(_ => true)?.state;
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Likewise this feels like a hacky approach to selecting the first (and only expected) item in the list, without failing if the list is empty due to renames, etc. Good enough? Other ideas?

Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think entities(isRainDelayStopTime)[0]?.state would have the same effect. If there are no rain delay entities entities(isRainDelayStopTime)[0] will be undefined.

return `Rain delay${ stop_time ? ` ends ${relativeTime(new Date(stop_time), this.hass!.locale!)}` : ''}`;
}

const programs = entities(isProgram).filter(stateActivated).map(osName);
if (hasRunOnce(entities)) programs.splice(0, 0, 'Once Program');
if (hasManual(entities)) programs.push('Stations Manually');
Expand Down
52 changes: 52 additions & 0 deletions src/relative_time.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,52 @@
//REF: https://github.com/custom-cards/custom-card-helpers/blob/master/src/datetime/relative_time.ts
//REF: https://github.com/home-assistant/frontend/blob/dev/src/common/datetime/relative_time.ts

import { selectUnit } from "@formatjs/intl-utils";

// REF: https://github.com/custom-cards/custom-card-helpers/blob/master/src/types.ts
// REF: https://github.com/home-assistant/frontend/blob/dev/src/data/translation.ts
enum NumberFormat {
language = "language",
system = "system",
comma_decimal = "comma_decimal",
decimal_comma = "decimal_comma",
space_comma = "space_comma",
none = "none",
}

export enum TimeFormat {
language = "language",
system = "system",
am_pm = "12",
twenty_four = "24",
}

interface MinBarFrontendLocaleData {
language: string;
}

const formatRelTimeMem =
(locale: MinBarFrontendLocaleData) =>
new Intl.RelativeTimeFormat(locale.language, { numeric: "auto" });

/**
* Calculate a string representing a date object as relative time from now.
*
* Example output: 5 minutes ago, in 3 days.
*/
export const relativeTime = (
from: Date,
locale: MinBarFrontendLocaleData,
to?: Date,
includeTense = true
): string => {
const diff = selectUnit(from, to);
if (includeTense) {
return formatRelTimeMem(locale).format(diff.value, diff.unit);
}
return Intl.NumberFormat(locale.language, {
style: "unit",
unit: diff.unit,
unitDisplay: "long",
}).format(Math.abs(diff.value));
};