From 1555126506c7ee9222a9411ae8ae31420444f13f Mon Sep 17 00:00:00 2001 From: Philipp Waller <1090452+philippwaller@users.noreply.github.com> Date: Sun, 17 Dec 2023 00:54:50 +0100 Subject: [PATCH 1/4] refactor(websocket): Enhance readability of debug messages --- src/modules/homeassistant/homeassistant.js | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/modules/homeassistant/homeassistant.js b/src/modules/homeassistant/homeassistant.js index 40f7e0d..a5486f9 100644 --- a/src/modules/homeassistant/homeassistant.js +++ b/src/modules/homeassistant/homeassistant.js @@ -92,9 +92,8 @@ export class Homeassistant { this.requests.set(command.id, callback); } - let commandJson = JSON.stringify(command); - console.log(`Sending HomeAssistant command ${commandJson}`) - this.websocket.send(commandJson); + console.log(`Sending HomeAssistant command:\n ${JSON.stringify(command,null,2)}`) + this.websocket.send(JSON.stringify(command)); } nextRequestId() { From 12b67951f0cbc149e358a419c9024672c48ea279 Mon Sep 17 00:00:00 2001 From: Philipp Waller <1090452+philippwaller@users.noreply.github.com> Date: Sun, 17 Dec 2023 01:02:48 +0100 Subject: [PATCH 2/4] refactor(websocket): Replace CallServiceCommand with ExecuteScriptCommand to enable Jinja support --- src/modules/homeassistant/actions/action.js | 15 +++++ .../homeassistant/actions/service-action.js | 36 ++++++++++++ src/modules/homeassistant/commands/command.js | 32 +++++++++++ .../commands/execute-script-command.js | 32 +++++++++++ .../commands/get-services-command.js | 15 +++++ .../commands/get-states-command.js | 15 +++++ .../commands/subscribe-events-command.js | 16 ++++++ src/modules/homeassistant/homeassistant.js | 56 ++++--------------- 8 files changed, 173 insertions(+), 44 deletions(-) create mode 100644 src/modules/homeassistant/actions/action.js create mode 100644 src/modules/homeassistant/actions/service-action.js create mode 100644 src/modules/homeassistant/commands/command.js create mode 100644 src/modules/homeassistant/commands/execute-script-command.js create mode 100644 src/modules/homeassistant/commands/get-services-command.js create mode 100644 src/modules/homeassistant/commands/get-states-command.js create mode 100644 src/modules/homeassistant/commands/subscribe-events-command.js diff --git a/src/modules/homeassistant/actions/action.js b/src/modules/homeassistant/actions/action.js new file mode 100644 index 0000000..61918fb --- /dev/null +++ b/src/modules/homeassistant/actions/action.js @@ -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"); + } + } +} diff --git a/src/modules/homeassistant/actions/service-action.js b/src/modules/homeassistant/actions/service-action.js new file mode 100644 index 0000000..9785646 --- /dev/null +++ b/src/modules/homeassistant/actions/service-action.js @@ -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}; + } +} diff --git a/src/modules/homeassistant/commands/command.js b/src/modules/homeassistant/commands/command.js new file mode 100644 index 0000000..3af81bd --- /dev/null +++ b/src/modules/homeassistant/commands/command.js @@ -0,0 +1,32 @@ +/** + * 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; + } +} + diff --git a/src/modules/homeassistant/commands/execute-script-command.js b/src/modules/homeassistant/commands/execute-script-command.js new file mode 100644 index 0000000..b0b6e89 --- /dev/null +++ b/src/modules/homeassistant/commands/execute-script-command.js @@ -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; + } +} diff --git a/src/modules/homeassistant/commands/get-services-command.js b/src/modules/homeassistant/commands/get-services-command.js new file mode 100644 index 0000000..1d1415a --- /dev/null +++ b/src/modules/homeassistant/commands/get-services-command.js @@ -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"); + } +} diff --git a/src/modules/homeassistant/commands/get-states-command.js b/src/modules/homeassistant/commands/get-states-command.js new file mode 100644 index 0000000..e416acb --- /dev/null +++ b/src/modules/homeassistant/commands/get-states-command.js @@ -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"); + } +} diff --git a/src/modules/homeassistant/commands/subscribe-events-command.js b/src/modules/homeassistant/commands/subscribe-events-command.js new file mode 100644 index 0000000..b90640b --- /dev/null +++ b/src/modules/homeassistant/commands/subscribe-events-command.js @@ -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"; + } +} diff --git a/src/modules/homeassistant/homeassistant.js b/src/modules/homeassistant/homeassistant.js index a5486f9..fe2295f 100644 --- a/src/modules/homeassistant/homeassistant.js +++ b/src/modules/homeassistant/homeassistant.js @@ -1,3 +1,9 @@ +import {ServiceAction} from "@/modules/homeassistant/actions/service-action"; +import {ExecuteScriptCommand} from "@/modules/homeassistant/commands/execute-script-command"; +import {SubscribeEventsCommand} from "@/modules/homeassistant/commands/subscribe-events-command"; +import {GetStatesCommand} from "@/modules/homeassistant/commands/get-states-command"; +import {GetServicesCommand} from "@/modules/homeassistant/commands/get-services-command"; + export class Homeassistant { constructor(url, accessToken, onReady, onError, onClose) { @@ -9,7 +15,7 @@ export class Homeassistant { this.onError = onError; this.websocket.onmessage = (evt) => this.handleMessage(evt); - this.websocket.onerror = () => { this.onError("Failed to connect to " + url) }; + this.websocket.onerror = () => {this.onError("Failed to connect to " + url)}; this.websocket.onclose = onClose; } @@ -78,13 +84,15 @@ export class Homeassistant { } subscribeEvents(callback) { - let subscribeEventCommand = new SubscribeEventCommand(this.nextRequestId()); + let subscribeEventCommand = new SubscribeEventsCommand(this.nextRequestId()); this.sendCommand(subscribeEventCommand, callback); } callService(service, domain, entity_id = null, serviceData = null, callback = null) { - let callServiceCommand = new CallServiceCommand(this.nextRequestId(), service, domain, entity_id, serviceData); - this.sendCommand(callServiceCommand, callback) + let executeScriptCmd = new ExecuteScriptCommand(this.nextRequestId(), [ + new ServiceAction(domain, service, entity_id ? [entity_id] : [], serviceData || {}) + ]); + this.sendCommand(executeScriptCmd, callback) } sendCommand(command, callback) { @@ -100,45 +108,5 @@ export class Homeassistant { this.requestIdSequence = this.requestIdSequence + 1; return this.requestIdSequence; } - -} - -class Command { - constructor(requestId, type) { - this.id = requestId; - this.type = type; - } -} - -class SubscribeEventCommand extends Command { - constructor(interactionCount) { - super(interactionCount, "subscribe_events"); - this.event_type = "state_changed"; - } -} - -class GetStatesCommand extends Command { - constructor(iterationCount) { - super(iterationCount, "get_states"); - } -} - -class GetServicesCommand extends Command { - constructor(iterationCount) { - super(iterationCount, "get_services"); - } } -class CallServiceCommand extends Command { - constructor(iterationCount, service, domain, entity_id, serviceData) { - super(iterationCount, "call_service"); - this.domain = domain; - this.service = service; - if (entity_id) { - this.target = {"entity_id": entity_id}; - } - if (serviceData) { - this.service_data = serviceData; - } - } -} From 81454b514cd75b334033bbff132bd7e8f90ae74a Mon Sep 17 00:00:00 2001 From: Philipp Waller <1090452+philippwaller@users.noreply.github.com> Date: Sun, 17 Dec 2023 01:04:54 +0100 Subject: [PATCH 3/4] docs: Update README.md with instructions on using jinja templates --- README.md | 25 ++++++++++++++++++++----- 1 file changed, 20 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 883adda..4ae9599 100644 --- a/README.md +++ b/README.md @@ -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) From e3c396ee9f7d187e8c2375357c8a6916c693bb1d Mon Sep 17 00:00:00 2001 From: Philipp Waller <1090452+philippwaller@users.noreply.github.com> Date: Sun, 17 Dec 2023 17:38:18 +0100 Subject: [PATCH 4/4] chore: format code --- src/modules/homeassistant/actions/action.js | 16 +- .../homeassistant/actions/service-action.js | 56 ++--- src/modules/homeassistant/commands/command.js | 45 ++-- .../commands/execute-script-command.js | 38 ++-- .../commands/get-services-command.js | 16 +- .../commands/get-states-command.js | 16 +- .../commands/subscribe-events-command.js | 18 +- src/modules/homeassistant/homeassistant.js | 194 +++++++++--------- 8 files changed, 199 insertions(+), 200 deletions(-) diff --git a/src/modules/homeassistant/actions/action.js b/src/modules/homeassistant/actions/action.js index 61918fb..d9c2722 100644 --- a/src/modules/homeassistant/actions/action.js +++ b/src/modules/homeassistant/actions/action.js @@ -3,13 +3,13 @@ * 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"); - } + /** + * 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') } + } } diff --git a/src/modules/homeassistant/actions/service-action.js b/src/modules/homeassistant/actions/service-action.js index 9785646..b53f2e1 100644 --- a/src/modules/homeassistant/actions/service-action.js +++ b/src/modules/homeassistant/actions/service-action.js @@ -1,36 +1,36 @@ -import {Action} from "@/modules/homeassistant/actions/action"; +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(); + /** + * 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}; + 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 } + } } diff --git a/src/modules/homeassistant/commands/command.js b/src/modules/homeassistant/commands/command.js index 3af81bd..5831090 100644 --- a/src/modules/homeassistant/commands/command.js +++ b/src/modules/homeassistant/commands/command.js @@ -3,30 +3,29 @@ * 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'); - } + /** + * 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 type !== 'string' || !type.trim()) { - throw new Error('type must be a non-empty string'); - } + if (typeof requestId !== 'number' || requestId < 0) { + throw new Error('requestId must be a non-negative number') + } - this.id = requestId; - this.type = type; + if (typeof type !== 'string' || !type.trim()) { + throw new Error('type must be a non-empty string') } -} + this.id = requestId + this.type = type + } +} diff --git a/src/modules/homeassistant/commands/execute-script-command.js b/src/modules/homeassistant/commands/execute-script-command.js index b0b6e89..c8e2a2e 100644 --- a/src/modules/homeassistant/commands/execute-script-command.js +++ b/src/modules/homeassistant/commands/execute-script-command.js @@ -1,5 +1,5 @@ -import {Command} from "@/modules/homeassistant/commands/command"; -import {Action} from "@/modules/homeassistant/actions/action"; +import { Command } from '@/modules/homeassistant/commands/command' +import { Action } from '@/modules/homeassistant/actions/action' /** * CallExecuteScriptCommand @@ -9,24 +9,24 @@ import {Action} from "@/modules/homeassistant/actions/action"; * 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"); + /** + * 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'); - } + if (!Array.isArray(actions)) { + throw new TypeError('Actions must be an array') + } - this.sequence = actions; + if (actions.some((action) => !(action instanceof Action))) { + throw new TypeError('Elements in actions must be Action instances or subclasses') } + + this.sequence = actions + } } diff --git a/src/modules/homeassistant/commands/get-services-command.js b/src/modules/homeassistant/commands/get-services-command.js index 1d1415a..0141041 100644 --- a/src/modules/homeassistant/commands/get-services-command.js +++ b/src/modules/homeassistant/commands/get-services-command.js @@ -1,15 +1,15 @@ -import {Command} from "@/modules/homeassistant/commands/command"; +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"); - } + /** + * Constructs a GetServicesCommand instance.* + * @param {number} requestId - The unique identifier for the command request. + */ + constructor(requestId) { + super(requestId, 'get_services') + } } diff --git a/src/modules/homeassistant/commands/get-states-command.js b/src/modules/homeassistant/commands/get-states-command.js index e416acb..d40e331 100644 --- a/src/modules/homeassistant/commands/get-states-command.js +++ b/src/modules/homeassistant/commands/get-states-command.js @@ -1,15 +1,15 @@ -import {Command} from "@/modules/homeassistant/commands/command"; +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"); - } + /** + * Constructs a GetStatesCommand instance. + * @param {number} requestId - The unique identifier for the command request. + */ + constructor(requestId) { + super(requestId, 'get_states') + } } diff --git a/src/modules/homeassistant/commands/subscribe-events-command.js b/src/modules/homeassistant/commands/subscribe-events-command.js index b90640b..420dd2d 100644 --- a/src/modules/homeassistant/commands/subscribe-events-command.js +++ b/src/modules/homeassistant/commands/subscribe-events-command.js @@ -1,16 +1,16 @@ -import {Command} from "@/modules/homeassistant/commands/command"; +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"; - } + /** + * 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' + } } diff --git a/src/modules/homeassistant/homeassistant.js b/src/modules/homeassistant/homeassistant.js index fe2295f..e4353b8 100644 --- a/src/modules/homeassistant/homeassistant.js +++ b/src/modules/homeassistant/homeassistant.js @@ -1,112 +1,112 @@ -import {ServiceAction} from "@/modules/homeassistant/actions/service-action"; -import {ExecuteScriptCommand} from "@/modules/homeassistant/commands/execute-script-command"; -import {SubscribeEventsCommand} from "@/modules/homeassistant/commands/subscribe-events-command"; -import {GetStatesCommand} from "@/modules/homeassistant/commands/get-states-command"; -import {GetServicesCommand} from "@/modules/homeassistant/commands/get-services-command"; +import { ServiceAction } from '@/modules/homeassistant/actions/service-action' +import { ExecuteScriptCommand } from '@/modules/homeassistant/commands/execute-script-command' +import { SubscribeEventsCommand } from '@/modules/homeassistant/commands/subscribe-events-command' +import { GetStatesCommand } from '@/modules/homeassistant/commands/get-states-command' +import { GetServicesCommand } from '@/modules/homeassistant/commands/get-services-command' export class Homeassistant { - - constructor(url, accessToken, onReady, onError, onClose) { - this.requests = new Map() - this.requestIdSequence = 1 - this.websocket = new WebSocket(url) - this.accessToken = accessToken; - this.onReady = onReady; - this.onError = onError; - - this.websocket.onmessage = (evt) => this.handleMessage(evt); - this.websocket.onerror = () => {this.onError("Failed to connect to " + url)}; - this.websocket.onclose = onClose; + constructor(url, accessToken, onReady, onError, onClose) { + this.requests = new Map() + this.requestIdSequence = 1 + this.websocket = new WebSocket(url) + this.accessToken = accessToken + this.onReady = onReady + this.onError = onError + + this.websocket.onmessage = (evt) => this.handleMessage(evt) + this.websocket.onerror = () => { + this.onError('Failed to connect to ' + url) } + this.websocket.onclose = onClose + } - close() { - this.websocket.onclose = null; - if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { - this.websocket.close(); - } + close() { + this.websocket.onclose = null + if (this.websocket && this.websocket.readyState === WebSocket.OPEN) { + this.websocket.close() } - - handleMessage(msg) { - let messageData = JSON.parse(msg.data); - - switch (messageData.type) { - case "auth_required": - this.sendAuthentication(); - break; - case "result": - if (!messageData.success) { - throw messageData.error.message - } - if (this.requests.has(messageData.id)) { - this.requests.get(messageData.id)(messageData.result); - } - break; - case "event": - if (this.requests.has(messageData.id)) { - this.requests.get(messageData.id)(messageData.event); - } - break; - case "auth_ok": - if (this.onReady) { - this.onReady(); - } - break; - case "auth_failed": - if (this.onError) { - this.onError(messageData.message); - } - break; - case "auth_invalid": - if (this.onError) { - this.onError(messageData.message); - } - break; + } + + handleMessage(msg) { + let messageData = JSON.parse(msg.data) + + switch (messageData.type) { + case 'auth_required': + this.sendAuthentication() + break + case 'result': + if (!messageData.success) { + throw messageData.error.message } - } - - sendAuthentication() { - let authMessage = { - "type": "auth", - "access_token": this.accessToken + if (this.requests.has(messageData.id)) { + this.requests.get(messageData.id)(messageData.result) } - - this.websocket.send(JSON.stringify(authMessage)) - } - - getStates(callback) { - let getStatesCommand = new GetStatesCommand(this.nextRequestId()); - this.sendCommand(getStatesCommand, callback); - } - - getServices(callback) { - let getServicesCommand = new GetServicesCommand(this.nextRequestId()); - this.sendCommand(getServicesCommand, callback) + break + case 'event': + if (this.requests.has(messageData.id)) { + this.requests.get(messageData.id)(messageData.event) + } + break + case 'auth_ok': + if (this.onReady) { + this.onReady() + } + break + case 'auth_failed': + if (this.onError) { + this.onError(messageData.message) + } + break + case 'auth_invalid': + if (this.onError) { + this.onError(messageData.message) + } + break } + } - subscribeEvents(callback) { - let subscribeEventCommand = new SubscribeEventsCommand(this.nextRequestId()); - this.sendCommand(subscribeEventCommand, callback); + sendAuthentication() { + let authMessage = { + type: 'auth', + access_token: this.accessToken } - callService(service, domain, entity_id = null, serviceData = null, callback = null) { - let executeScriptCmd = new ExecuteScriptCommand(this.nextRequestId(), [ - new ServiceAction(domain, service, entity_id ? [entity_id] : [], serviceData || {}) - ]); - this.sendCommand(executeScriptCmd, callback) + this.websocket.send(JSON.stringify(authMessage)) + } + + getStates(callback) { + let getStatesCommand = new GetStatesCommand(this.nextRequestId()) + this.sendCommand(getStatesCommand, callback) + } + + getServices(callback) { + let getServicesCommand = new GetServicesCommand(this.nextRequestId()) + this.sendCommand(getServicesCommand, callback) + } + + subscribeEvents(callback) { + let subscribeEventCommand = new SubscribeEventsCommand(this.nextRequestId()) + this.sendCommand(subscribeEventCommand, callback) + } + + callService(service, domain, entity_id = null, serviceData = null, callback = null) { + let executeScriptCmd = new ExecuteScriptCommand(this.nextRequestId(), [ + new ServiceAction(domain, service, entity_id ? [entity_id] : [], serviceData || {}) + ]) + this.sendCommand(executeScriptCmd, callback) + } + + sendCommand(command, callback) { + if (callback) { + this.requests.set(command.id, callback) } - sendCommand(command, callback) { - if (callback) { - this.requests.set(command.id, callback); - } - - console.log(`Sending HomeAssistant command:\n ${JSON.stringify(command,null,2)}`) - this.websocket.send(JSON.stringify(command)); - } + console.log(`Sending HomeAssistant command:\n ${JSON.stringify(command, null, 2)}`) + this.websocket.send(JSON.stringify(command)) + } - nextRequestId() { - this.requestIdSequence = this.requestIdSequence + 1; - return this.requestIdSequence; - } + nextRequestId() { + this.requestIdSequence = this.requestIdSequence + 1 + return this.requestIdSequence + } } -