Skip to content

Commit

Permalink
Merge pull request #218 from philippwaller/feat/template-support
Browse files Browse the repository at this point in the history
Add support for Jinja templates
  • Loading branch information
cgiesche committed Dec 20, 2023
2 parents a9bd216 + e3c396e commit 8293b7a
Show file tree
Hide file tree
Showing 9 changed files with 277 additions and 135 deletions.
25 changes: 20 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -80,23 +80,38 @@ After you save your Home Assistant Settings, the plugin will automatically try t
"rgb_color": [255, 0, 0]
}
```
You can also use [Jinja2 templates](https://www.home-assistant.io/docs/configuration/templating/) to dynamically process data based on the states or attributes of your Home Assistant entities. See [Using Jinja2 Templates in Service Data JSON](#Advanced-configuration) for more information.

![img.png](doc/entity_settings.png)

### Advanced configuration
* Custom Title: Enable to override the main Title of this button. **You have to clear the main Title field on top to make this work!**
* **Custom Title:** Enable to override the main Title of this button. **You have to clear the main Title field on top to make this work!**
* Title Template: A [nunjucks template](https://mozilla.github.io/nunjucks/templating.html) that will be used as the button's title. You can use any of the variables (depending on the selected entity) that are shown below the text-field. For example `{{temperature}}°C` or `{{friendly_name}}` or (this won't fit the button, but you get the idea) `The pressure is {{pressure}} and the wind speed is {{wind_speed}}.`
* The variable `{{state}}` always contains the "main state" of an entity (for example "on" and "off" for buttons or "12.4" for temperature sensors)
* The variable `{{unit_of_measurement}}` often contains the ... unit of measurement ... of a sensor's state (for example "°C" for a temperature sensor)

* Custom Labels: Every button can display up to 4 lines of information
* **Custom Labels:** Every button can display up to 4 lines of information
* Each line in the text-box represents one line on the button
* Depending on if there is an icon or a title for the entity, you may need to leave blank lines in order to not mess up the layout :)
* You can use [nunjucks template](https://mozilla.github.io/nunjucks/templating.html) for dynamic content (see above).

After you hit the save button, the button should immediately show the new configuration.

![img.png](doc/custom_labels.png)
After you hit the save button, the button should immediately show the new configuration.

![img.png](doc/custom_labels.png)

* **Using Jinja2 Templates in Service Data JSON**
* **Jinja2 Template Integration:** You can incorporate Jinja2 templates within the Service Data JSON to dynamically
process data based on the states or attributes of your Home Assistant entities.
* **Encapsulation with Raw Tags:** It's crucial to enclose Jinja2 templates within `{% raw %}` and `{% endraw %}`
tags. This encapsulation ensures that the StreamDeck plugin processes these templates as Jinja2, distinct from any
Nunjucks templates you might use elsewhere in your configurations.
* **Example of Jinja2 Template Usage:**

```json
{
"temperature": "{% raw %}{{ state_attr('climate.ff_office_heating','temperature') + 0.5 }}{% endraw %}"
}
```

# Happy? Consider donating me a coffee :)
[![buy me a coffee](https://www.paypalobjects.com/en_US/i/btn/btn_donate_SM.gif)](https://www.paypal.com/donate?hosted_button_id=3UKRJEJVWV9H4)
Expand Down
15 changes: 15 additions & 0 deletions src/modules/homeassistant/actions/action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
/**
* Abstract base class for actions. It serves as a foundation for all specific action types and is not
* intended for direct instantiation.
*/
export class Action {
/**
* Constructs an Action instance. Blocks direct instantiation of this abstract class.
* @throws {TypeError} If directly instantiated.
*/
constructor() {
if (new.target === Action) {
throw new TypeError('Cannot instantiate abstract class Action directly')
}
}
}
36 changes: 36 additions & 0 deletions src/modules/homeassistant/actions/service-action.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
import { Action } from '@/modules/homeassistant/actions/action'

/**
* ServiceAction, extending Action, facilitates interactions with HomeAssistant services.
*/
export class ServiceAction extends Action {
/**
* Constructs a ServiceAction instance.
* @param {string} domain - Service domain, must be a non-empty string.
* @param {string} service - Service name, must be a non-empty string.
* @param {Array} [entity_id=[]] - Target entity IDs array.
* @param {Object} [serviceData={}] - Additional service data.
* @throws {Error} if 'domain' or 'service' are empty or not strings.
* @throws {TypeError} if 'entity_id' is not an array or 'serviceData' is not an object.
*/
constructor(domain, service, entity_id, serviceData) {
super()

if (typeof domain !== 'string' || !domain.trim()) {
throw new Error('Domain must be a non-empty string')
}
if (typeof service !== 'string' || !service.trim()) {
throw new Error('Service must be a non-empty string')
}
if (!Array.isArray(entity_id)) {
throw new TypeError('entity_id must be an array')
}
if (typeof serviceData !== 'object' || serviceData === null) {
throw new TypeError('serviceData must be an object')
}

this.service = `${domain}.${service}`
this.data = serviceData
this.target = { entity_id: entity_id }
}
}
31 changes: 31 additions & 0 deletions src/modules/homeassistant/commands/command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/**
* The Command class acts as an abstract base class for creating commands
* that can be used to interact with the HomeAssistant WebSocket API.
*/
export class Command {
/**
* Constructs a Command instance.
* @param {number} requestId - The unique identifier for the command request.
* @param {string} type - The type of the command. Must be a non-empty string.
* @throws {TypeError} If an attempt is made to instantiate Command directly.
* @throws {Error} If the requestId is not a non-negative number.
* @throws {Error} If the type is not a non-empty string.
*/
constructor(requestId, type) {
// Prevent direct instantiation of this abstract class.
if (new.target === Command) {
throw new TypeError('Cannot instantiate abstract class Command directly')
}

if (typeof requestId !== 'number' || requestId < 0) {
throw new Error('requestId must be a non-negative number')
}

if (typeof type !== 'string' || !type.trim()) {
throw new Error('type must be a non-empty string')
}

this.id = requestId
this.type = type
}
}
32 changes: 32 additions & 0 deletions src/modules/homeassistant/commands/execute-script-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { Command } from '@/modules/homeassistant/commands/command'
import { Action } from '@/modules/homeassistant/actions/action'

/**
* CallExecuteScriptCommand
*
* Facilitates the execution of multiple actions, including service calls, in a single command. This command
* is a substantial improvement over the "call_service" command, as it incorporates and evaluates Jinja templates.
* This enhancement enables more dynamic and context-sensitive operations within HomeAssistant.
*/
export class ExecuteScriptCommand extends Command {
/**
* Constructs a CallExecuteScriptCommand instance.
*
* @param {number} requestId - Number of iterations for execution. Must be non-negative.
* @param {Action[]} [actions=[]] - Array of ScriptCommand instances. Optional, defaults to empty.
* @throws {TypeError} if actions is not an array or has non-Action elements.
*/
constructor(requestId, actions = []) {
super(requestId, 'execute_script')

if (!Array.isArray(actions)) {
throw new TypeError('Actions must be an array')
}

if (actions.some((action) => !(action instanceof Action))) {
throw new TypeError('Elements in actions must be Action instances or subclasses')
}

this.sequence = actions
}
}
15 changes: 15 additions & 0 deletions src/modules/homeassistant/commands/get-services-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@/modules/homeassistant/commands/command'

/**
* The GetServicesCommand class, a subclass of Command, is used for requesting
* service information from HomeAssistant.
*/
export class GetServicesCommand extends Command {
/**
* Constructs a GetServicesCommand instance.*
* @param {number} requestId - The unique identifier for the command request.
*/
constructor(requestId) {
super(requestId, 'get_services')
}
}
15 changes: 15 additions & 0 deletions src/modules/homeassistant/commands/get-states-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import { Command } from '@/modules/homeassistant/commands/command'

/**
* The GetStatesCommand class, a subclass of Command, handles the retrieval of
* state information from HomeAssistant.
*/
export class GetStatesCommand extends Command {
/**
* Constructs a GetStatesCommand instance.
* @param {number} requestId - The unique identifier for the command request.
*/
constructor(requestId) {
super(requestId, 'get_states')
}
}
16 changes: 16 additions & 0 deletions src/modules/homeassistant/commands/subscribe-events-command.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
import { Command } from '@/modules/homeassistant/commands/command'

/**
* The SubscribeEventCommand class, a subclass of Command, specifically handles
* subscription to event types in HomeAssistant.
*/
export class SubscribeEventsCommand extends Command {
/**
* Constructs a SubscribeEventCommand instance.
* @param {number} requestId - The unique identifier for the command request.
*/
constructor(requestId) {
super(requestId, 'subscribe_events')
this.event_type = 'state_changed'
}
}
Loading

0 comments on commit 8293b7a

Please sign in to comment.