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

2.0 – the breaking changes release – WIP #225

Merged
merged 20 commits into from
Apr 5, 2021
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
Show all changes
20 commits
Select commit Hold shift + click to select a range
7f66a55
feat: Move all header configuration to a new header section
nervetattoo Mar 30, 2021
8d7534c
chore: Configure semantic-release/git to bump package.json
nervetattoo Mar 31, 2021
6747a71
chore: Remove custom release branches, defaults should work just fine
nervetattoo Mar 31, 2021
d221728
feat: Fine grained setpoint control
nervetattoo Mar 31, 2021
9ee1af9
feat: Support custom domain/service calls and passing extra data. Fix…
nervetattoo Mar 31, 2021
a38f5e3
chore: Add missing dep semantic-release/git
nervetattoo Mar 31, 2021
355ec41
fix: Attempt to hide unit when there is no value present. Fixes #210
nervetattoo Mar 31, 2021
c890771
fix: Translation of sensor state and state/temperature
nervetattoo Mar 31, 2021
9de34db
fix: Dont display unit as string false for current temp.
nervetattoo Apr 1, 2021
3081628
chore: Remove unused dependencies
nervetattoo Apr 3, 2021
3f03eef
refactor: Clean up unused types and misplaced types
nervetattoo Apr 4, 2021
029acde
doc: Change buymeacoffee button
nervetattoo Apr 4, 2021
e1000b9
refactor: Separate card config interface to its own module
nervetattoo Apr 4, 2021
e0f461c
feat: New layout config option
nervetattoo Apr 4, 2021
f39a56d
fix: Bring back support for name:false
nervetattoo Apr 5, 2021
b224ef7
refactor: Simplify components/header code
nervetattoo Apr 5, 2021
629fc36
feat: Move top level layout options in control to new layout option
nervetattoo Apr 5, 2021
3b8e8a2
refactor: Simplify mode options handling
nervetattoo Apr 5, 2021
15a5423
chore: Rename hacs gh action to descriptive file name
nervetattoo Apr 5, 2021
a92afa6
fix: Slightly improve responsiveness of setpoint vs sensors
nervetattoo Apr 5, 2021
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
10 changes: 7 additions & 3 deletions .releaserc.json
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
{
"release": {
"branches": ["master"]
},
"plugins": [
"@semantic-release/commit-analyzer",
"@semantic-release/release-notes-generator",
[
"@semantic-release/git",
{
"assets": ["package.json"],
"message": "chore(release): ${nextRelease.version} [skip ci]\n\n${nextRelease.notes}"
}
],
[
"@semantic-release/github",
{
Expand Down
99 changes: 79 additions & 20 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -57,35 +57,21 @@ resources:
## Available configuration options:

- `entity` _string_: The thermostat entity id **required**
- `toggle_entity` _string|object_: An entity id to create a toggle in the header for. This gives the option to control a separate entity which can be related to the thermostat entity (like a switch, or input_boolean)
- `entity_id` _string_: The entity id to create the header for
- `name` _string|bool_: Set the label to be shown to the left of the toggle. Set to true to show the friendly name of the toggle_entity
- `name` _string_: Override the card name. Default is to use the friendly_name of the thermostat provided
- `show_header` _bool_: Set to false to hide card name and icon
- `header` _false|Header object_: See section about header config
- `setpoints` _false|Setpoints object_: See section about header config
- `service` _object_: Must specify both domain+service if overriding
- `domain` _string_: Override the service call domain
- `service` _string_: Override the service call name
- `data` _object_: Send extra data with the service call
- `unit` _string|bool_: Override the unit to display. Set to false to hide unit
- `decimals` _number_: Specify number of decimals to use: 1 or 0
- `fallback` _string_: Specify a text to display if a valid set point can't be determined. Defaults to `N/A`
- `icon` _string|object_: Show an icon next to the card name. You can also pass an object to specify specific icons. Current value is taken from attributes.hvac_action when available, or state as fallback.
- `auto`: _string_ Use this icon for hvac_action auto. Default mdi:radiator
- `cooling`: _string_ Use this icon for hvac_action cooling. Default mdi:snowflake
- `fan`: _string_ Use this icon for hvac_action fan. Default mdi:fan
- `heating`: _string_ Use this icon for hvac_action heating. Default mdi:radiator
- `idle`: _string_: Use this icon for hvac_action idle. Default mdi:radiator-disabled
- `"off"`: _string_ Use this icon for hvac_action off. Default mdi:radiator-off
- `auto`: _string_ Use this icon for state auto. Default hass:autorenew
- `cool`: _string_ Use this icon for state cooling. Default hass:snowflake
- `dry`: _string_: Use this icon for state dry. Default hass:water-percent
- `fan_only`: _string_ Use this icon for state fan. Default hass:fan
- `heat`: _string_ Use this icon for state heat. Default hass:autorenew
- `heat_cool`: _string_: Use this icon for state heat_cool. Default hass:fire
- `"off"`: _string_ Use this icon for state off. Default hass:power
- `step_size` _number_: Override the default 0.5 step size for increasing/decreasing the temperature
- `step_layout` _string_: `row` or `column` (default). Using `row` will make the card more compact
- `label` _object_: Override untranslated labels
- `temperature`: _string_ Override Temperature label
- `state`: _string_ Override State label
- `hide` _object_: Control specifically information fields to show. Defaults to showing everything
- `setpoint`: _bool_ (Default to `false`)
- `temperature`: _bool_ (Default to `false`)
- `state`: _bool_ (Default to `false`)
- `control` _object|array_ (From 0.27)
Expand All @@ -104,10 +90,83 @@ resources:
- `icon` _string_: Specify an icon to use instead of a name
- `attribute` _string_: The key for an attribute to use instead of state. If this sensor has no entity it will use the main entity's attributes
- `unit` _string_: When specifying an attribute you can manually set the unit to display

## Header config

> New in 2.0. Old ways of defining toggle_entity, faults, name and icon are no longer supported

Hiding the entire header is done with `header: false`
If you pass an object you can pass any of the following keys.
Example:

```yaml
header:
name: Overriden name
toggle:
entity: switch.light
name: Light
icon: mdi:sofa
faults:
- entity: switch.light
```

- `name` _string_: Override the card name. Default is to use the friendly_name of the thermostat provided
- `toggle` _object_: An entity id to create a toggle in the header for. This gives the option to control a separate entity which can be related to the thermostat entity (like a switch, or input_boolean)
- `entity` _string_: The entity id to create the header for
- `name` _string|bool_: Set the label to be shown to the left of the toggle. Set to true to show the friendly name of the toggle_entity
- `faults` _array|false_: Show fault conditions as active/inactive icons in the header
- `entity` _string_: A binary sensor entity id
- `icon` _string_: Override the entity icon
- `hide_inactive` _bool_: Hide the fault icon when inactive (Default to `false`)
- `icon` _string|object_: Show an icon next to the card name. You can also pass an object to specify specific icons. Current value is taken from attributes.hvac_action when available, or state as fallback.
- `auto`: _string_ Use this icon for hvac_action auto. Default mdi:radiator
- `cooling`: _string_ Use this icon for hvac_action cooling. Default mdi:snowflake
- `fan`: _string_ Use this icon for hvac_action fan. Default mdi:fan
- `heating`: _string_ Use this icon for hvac_action heating. Default mdi:radiator
- `idle`: _string_: Use this icon for hvac_action idle. Default mdi:radiator-disabled
- `"off"`: _string_ Use this icon for hvac_action off. Default mdi:radiator-off
- `auto`: _string_ Use this icon for state auto. Default hass:autorenew
- `cool`: _string_ Use this icon for state cooling. Default hass:snowflake
- `dry`: _string_: Use this icon for state dry. Default hass:water-percent
- `fan_only`: _string_ Use this icon for state fan. Default hass:fan
- `heat`: _string_ Use this icon for state heat. Default hass:autorenew
- `heat_cool`: _string_: Use this icon for state heat_cool. Default hass:fire
- `"off"`: _string_ Use this icon for state off. Default hass:power

## Setpoints config

> New in 2.0. Old ways of hiding setpoints is deprecated

If you specify setpoints manually you must include all setpoints you want included.
Normally there are only two possibilities here; `temperature` or `target_temp_high` + `target_temp_low`. Single or dual thermostats. But, theoretically there could be multiple setpoints and this aims to support any permutation.
The new feature in 2.0 is the ability to hide one of the two setpoints for dual thermostats.

To manually specify to use the `temperature` attribute as a setpoint you do:

```yaml
setpoints:
temperature:
```

For dual thermostats:

```yaml
setpoints:
target_temp_low:
target_temp_high:
```

To hide one of the dual setpoints:

```yaml
setpoints:
target_temp_low:
hide: true
target_temp_high:
```

For climate devices supporting more setpoints you can include as many as you like.
Automatic detection of set points only work for the single/dual cases.

## Usage of the control config

Expand Down
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"@rollup/plugin-json": "^4.1.0",
"@rollup/plugin-node-resolve": "^11.2.1",
"@rollup/plugin-typescript": "^8.2.1",
"@semantic-release/git": "^9.0.0",
"@semantic-release/github": "^7.2.0",
"@size-limit/file": "^4.10.1",
"@size-limit/time": "^4.10.2",
Expand Down
48 changes: 23 additions & 25 deletions src/components/header.ts
Original file line number Diff line number Diff line change
@@ -1,29 +1,27 @@
import { html } from 'lit-element'
import { html, nothing } from 'lit-html'
import { LooseObject } from '../types'
import { HeaderData } from '../config/header'

type HeaderOptions = {
name: string | boolean
icon: string | LooseObject
faults
toggle_entity
header: false | HeaderData
entity: LooseObject
openEntityPopover
toggle_entity_label
toggleEntityChanged
}

export default function renderHeader({
name,
icon,
faults,
toggle_entity,
toggle_entity_label,
header,
toggleEntityChanged,
entity,
openEntityPopover,
}: HeaderOptions) {
if (header === false) {
return nothing
}

const action = entity.attributes.hvac_action || entity.state
if (typeof icon === 'object') {
let icon = header.icon
if (typeof header.icon === 'object') {
icon = icon?.[action] ?? false
}

Expand All @@ -37,14 +35,13 @@ export default function renderHeader({
${(icon &&
html` <ha-icon class="header__icon" .icon=${icon}></ha-icon> `) ||
''}
<h2 class="header__title">${name}</h2>
<h2 class="header__title">${header?.name}</h2>
</div>
${faults ? renderFaults({ faults, openEntityPopover }) : ''}
${toggle_entity
${renderFaults({ faults: header.faults, openEntityPopover })}
${header.toggle
? renderToggle({
toggle: header.toggle,
openEntityPopover,
toggle_entity,
toggle_entity_label,
toggleEntityChanged,
})
: ''}
Expand All @@ -57,6 +54,9 @@ type FaultsOptions = {
openEntityPopover
}
function renderFaults({ faults, openEntityPopover }: FaultsOptions) {
if (faults.length === 0) {
return nothing
}
const faultHtml = faults.map(({ icon, hide_inactive, state }) => {
return html` <ha-icon
class="fault-icon ${state.state === 'on'
Expand All @@ -74,25 +74,23 @@ function renderFaults({ faults, openEntityPopover }: FaultsOptions) {

type ToggleOptions = {
openEntityPopover
toggle_entity
toggle_entity_label
toggle
toggleEntityChanged
}
function renderToggle({
openEntityPopover,
toggle_entity,
toggle_entity_label,
toggle,
toggleEntityChanged,
openEntityPopover,
}: ToggleOptions) {
return html`
<div style="margin-left: auto;">
<span
class="clickable toggle-label"
@click="${() => openEntityPopover(toggle_entity?.entity_id)}"
>${toggle_entity_label}
@click="${() => openEntityPopover(toggle.entity)}"
>${toggle.label}
</span>
<ha-switch
.checked=${toggle_entity?.state === 'on'}
.checked=${toggle.entity?.state === 'on'}
@change=${toggleEntityChanged}
></ha-switch>
</div>
Expand Down
97 changes: 97 additions & 0 deletions src/config/header.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
import { HASS, HAState, LooseObject, Fault } from '../types'

export const STATE_ICONS = {
auto: 'mdi:radiator',
cooling: 'mdi:snowflake',
fan: 'mdi:fan',
heating: 'mdi:radiator',
idle: 'mdi:radiator-disabled',
off: 'mdi:radiator-off',
}

export const MODE_ICONS = {
auto: 'hass:autorenew',
cool: 'hass:snowflake',
dry: 'hass:water-percent',
fan_only: 'hass:fan',
heat_cool: 'hass:autorenew',
heat: 'hass:fire',
off: 'hass:power',
}

type Icon = string | false | LooseObject
type Name = string | false
export interface HeaderConfig {
name?: Name
icon?: Icon
faults?: Array<Fault>
toggle?: ToggleConfig
}

export interface HeaderData {
name?: Name
icon: Icon
faults?: Array<Fault>
toggle?: Toggle
}

export interface Toggle {
entity: HAState
label: string
}
export type ToggleConfig = { entity: string; name?: string | boolean }

export default function parseHeaderConfig(
config: false | HeaderConfig,
entity,
hass: HASS
): false | HeaderData {
if (config === false) return false

let name
if (typeof config?.name === 'string') {
name = config.name
} else if (config?.name === false) {
name = false
} else {
name = entity.attributes.friendly_name
}

let icon: Icon = entity.attributes.hvac_action ? STATE_ICONS : MODE_ICONS
if (typeof config?.icon !== 'undefined') {
icon = config.icon
}

return {
name,
icon,
toggle: config?.toggle ? parseToggle(config.toggle, hass) : null,
faults: parseFaults(config?.faults, hass),
}
}

function parseToggle(config: ToggleConfig, hass): Toggle {
const entity: HAState = hass.states[config.entity]

let label = ''
if (config?.name === true) {
label = entity.attributes.name
} else {
label = (config?.name as string) ?? ''
}

return { entity, label }
}

function parseFaults(config: Array<Fault>, hass: HASS) {
if (Array.isArray(config)) {
return config.map(({ entity, ...rest }: Fault) => {
return {
...rest,
state: hass.states[entity],
entity,
}
})
}
return []
}
11 changes: 11 additions & 0 deletions src/config/service.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
import { Service } from '../types'

export default function parseServie(config: false | Service): Service {
if (!config) {
return {
domain: 'climate',
service: 'set_temperature',
}
}
return config
}
30 changes: 30 additions & 0 deletions src/config/setpoints.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { Setpoints } from '../types'
import getEntityType from '../getEntityType'
const DUAL = 'dual' as const

export default function parseSetpoints(
setpoints: Setpoints | false,
attributes: any
) {
if (setpoints) {
const def = Object.keys(setpoints)
return def.reduce((result, name: string) => {
const sp = setpoints[name]
if (sp?.hide) return result
return {
...result,
[name]: attributes?.[name],
}
}, {})
}
const entityType = getEntityType(attributes)
if (entityType === DUAL) {
return {
target_temp_low: attributes.target_temp_low,
target_temp_high: attributes.target_temp_high,
}
}
return {
temperature: attributes.temperature,
}
}