diff --git a/packages/nodes-base/credentials/HomeAssistantApi.credentials.ts b/packages/nodes-base/credentials/HomeAssistantApi.credentials.ts
new file mode 100644
index 0000000000..802561ba98
--- /dev/null
+++ b/packages/nodes-base/credentials/HomeAssistantApi.credentials.ts
@@ -0,0 +1,36 @@
+import {
+ ICredentialType,
+ NodePropertyTypes,
+} from 'n8n-workflow';
+
+export class HomeAssistantApi implements ICredentialType {
+ name = 'homeAssistantApi';
+ displayName = 'Home Assistant API';
+ documentationUrl = 'homeAssistant';
+ properties = [
+ {
+ displayName: 'Host',
+ name: 'host',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ {
+ displayName: 'Port',
+ name: 'port',
+ type: 'number' as NodePropertyTypes,
+ default: 8123,
+ },
+ {
+ displayName: 'SSL',
+ name: 'ssl',
+ type: 'boolean' as NodePropertyTypes,
+ default: false,
+ },
+ {
+ displayName: 'Access Token',
+ name: 'accessToken',
+ type: 'string' as NodePropertyTypes,
+ default: '',
+ },
+ ];
+}
diff --git a/packages/nodes-base/nodes/HomeAssistant/CameraProxyDescription.ts b/packages/nodes-base/nodes/HomeAssistant/CameraProxyDescription.ts
new file mode 100644
index 0000000000..35764bfc2a
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/CameraProxyDescription.ts
@@ -0,0 +1,69 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const cameraProxyOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'cameraProxy',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Get Screenshot',
+ value: 'getScreenshot',
+ description: 'Get the camera screenshot',
+ },
+ ],
+ default: 'getScreenshot',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const cameraProxyFields = [
+ /* -------------------------------------------------------------------------- */
+ /* cameraProxy:getScreenshot */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Camera Entity ID',
+ name: 'cameraEntityId',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ operation: [
+ 'getScreenshot',
+ ],
+ resource: [
+ 'cameraProxy',
+ ],
+ },
+ },
+ description: 'The camera entity ID.',
+ },
+ {
+ displayName: 'Binary Property',
+ name: 'binaryPropertyName',
+ type: 'string',
+ required: true,
+ default: 'data',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getScreenshot',
+ ],
+ resource: [
+ 'cameraProxy',
+ ],
+ },
+ },
+ description: 'Name of the binary property to which to
write the data of the read file.',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/ConfigDescription.ts b/packages/nodes-base/nodes/HomeAssistant/ConfigDescription.ts
new file mode 100644
index 0000000000..1606ab4df5
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/ConfigDescription.ts
@@ -0,0 +1,32 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const configOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'config',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get the configuration',
+ },
+ {
+ name: 'Check Configuration',
+ value: 'check',
+ description: 'Check the configuration',
+ },
+ ],
+ default: 'get',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/EventDescription.ts b/packages/nodes-base/nodes/HomeAssistant/EventDescription.ts
new file mode 100644
index 0000000000..8a5dfb76f9
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/EventDescription.ts
@@ -0,0 +1,144 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const eventOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'event',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all events',
+ },
+ {
+ name: 'Post',
+ value: 'post',
+ description: 'Post an event',
+ },
+ ],
+ default: 'getAll',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const eventFields = [
+
+ /* -------------------------------------------------------------------------- */
+ /* event:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'event',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'event',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 50,
+ description: 'How many results to return.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* event:post */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Event Type',
+ name: 'eventType',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: [
+ 'post',
+ ],
+ resource: [
+ 'event',
+ ],
+ },
+ },
+ required: true,
+ default: '',
+ description: 'The Entity ID for which an event will be created.',
+ },
+ {
+ displayName: 'Event Attributes',
+ name: 'eventAttributes',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ placeholder: 'Add Attribute',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'event',
+ ],
+ operation: [
+ 'post',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Attributes',
+ name: 'attributes',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Name of the attribute.',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value of the attribute.',
+ },
+ ],
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/GenericFunctions.ts b/packages/nodes-base/nodes/HomeAssistant/GenericFunctions.ts
new file mode 100644
index 0000000000..a4e6b3f9d5
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/GenericFunctions.ts
@@ -0,0 +1,42 @@
+import {
+ OptionsWithUri
+} from 'request';
+
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ NodeApiError,
+ NodeOperationError,
+} from 'n8n-workflow';
+
+export async function homeAssistantApiRequest(this: IExecuteFunctions, method: string, resource: string, body: IDataObject = {}, qs: IDataObject = {}, uri?: string, option: IDataObject = {}) {
+ const credentials = this.getCredentials('homeAssistantApi');
+
+ if (credentials === undefined) {
+ throw new NodeOperationError(this.getNode(), 'No credentials got returned!');
+ }
+
+ let options: OptionsWithUri = {
+ headers: {
+ Authorization: `Bearer ${credentials.accessToken}`,
+ },
+ method,
+ qs,
+ body,
+ uri: uri ?? `${credentials.ssl === true ? 'https' : 'http'}://${credentials.host}:${credentials.port}/api${resource}`,
+ json: true,
+ };
+
+ options = Object.assign({}, options, option);
+ if (Object.keys(options.body).length === 0) {
+ delete options.body;
+ }
+ try {
+ return await this.helpers.request(options);
+ } catch (error) {
+ throw new NodeApiError(this.getNode(), error);
+ }
+}
diff --git a/packages/nodes-base/nodes/HomeAssistant/HistoryDescription.ts b/packages/nodes-base/nodes/HomeAssistant/HistoryDescription.ts
new file mode 100644
index 0000000000..53b466d6e4
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/HistoryDescription.ts
@@ -0,0 +1,128 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const historyOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'history',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all state changes',
+ },
+ ],
+ default: 'getAll',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const historyFields = [
+ /* -------------------------------------------------------------------------- */
+ /* history:getLogbookEntries */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'history',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'history',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 50,
+ description: 'How many results to return.',
+ },
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'history',
+ ],
+ operation: [
+ 'getAll',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'End Time',
+ name: 'endTime',
+ type: 'dateTime',
+ default: '',
+ description: 'The end of the period.',
+ },
+ {
+ displayName: 'Entity IDs',
+ name: 'entityIds',
+ type: 'string',
+ default: '',
+ description: 'The entities IDs separated by comma.',
+ },
+ {
+ displayName: 'Minimal Response',
+ name: 'minimalResponse',
+ type: 'boolean',
+ default: false,
+ description: 'To only return last_changed and state for states.',
+ },
+ {
+ displayName: 'Significant Changes Only',
+ name: 'significantChangesOnly',
+ type: 'boolean',
+ default: false,
+ description: 'Only return significant state changes.',
+ },
+ {
+ displayName: 'Start Time',
+ name: 'startTime',
+ type: 'dateTime',
+ default: '',
+ description: 'The beginning of the period.',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.json b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.json
new file mode 100644
index 0000000000..d5b99fa9af
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.json
@@ -0,0 +1,20 @@
+{
+ "node": "n8n-nodes-base.homeAssistant",
+ "nodeVersion": "1.0",
+ "codexVersion": "1.0",
+ "categories": [
+ "Miscellaneous"
+ ],
+ "resources": {
+ "credentialDocumentation": [
+ {
+ "url": "https://docs.n8n.io/credentials/homeAssistant"
+ }
+ ],
+ "primaryDocumentation": [
+ {
+ "url": "https://docs.n8n.io/nodes/n8n-nodes-base.homeAssistant/"
+ }
+ ]
+ }
+}
diff --git a/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts
new file mode 100644
index 0000000000..e8508685e9
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/HomeAssistant.node.ts
@@ -0,0 +1,377 @@
+import {
+ IExecuteFunctions,
+} from 'n8n-core';
+
+import {
+ IDataObject,
+ INodeExecutionData,
+ INodeType,
+ INodeTypeDescription,
+} from 'n8n-workflow';
+
+import {
+ configOperations,
+} from './ConfigDescription';
+
+import {
+ serviceFields,
+ serviceOperations,
+} from './ServiceDescription';
+
+import {
+ stateFields,
+ stateOperations,
+} from './StateDescription';
+
+import {
+ eventFields,
+ eventOperations,
+} from './EventDescription';
+
+import {
+ logFields,
+ logOperations,
+} from './LogDescription';
+
+import {
+ templateFields,
+ templateOperations,
+} from './TemplateDescription';
+
+import {
+ historyFields,
+ historyOperations,
+} from './HistoryDescription';
+
+import {
+ cameraProxyFields,
+ cameraProxyOperations,
+} from './CameraProxyDescription';
+
+import {
+ homeAssistantApiRequest,
+} from './GenericFunctions';
+
+export class HomeAssistant implements INodeType {
+ description: INodeTypeDescription = {
+ displayName: 'Home Assistant',
+ name: 'homeAssistant',
+ icon: 'file:homeAssistant.svg',
+ group: ['output'],
+ version: 1,
+ subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
+ description: 'Consume Home Assistant API',
+ defaults: {
+ name: 'Home Assistant',
+ color: '#3578e5',
+ },
+ inputs: ['main'],
+ outputs: ['main'],
+ credentials: [
+ {
+ name: 'homeAssistantApi',
+ required: true,
+ },
+ ],
+ properties: [
+ {
+ displayName: 'Resource',
+ name: 'resource',
+ type: 'options',
+ options: [
+ {
+ name: 'Camera Proxy',
+ value: 'cameraProxy',
+ },
+ {
+ name: 'Config',
+ value: 'config',
+ },
+ // {
+ // name: 'Event',
+ // value: 'event',
+ // },
+ // {
+ // name: 'History',
+ // value: 'history',
+ // },
+ {
+ name: 'Log',
+ value: 'log',
+ },
+ {
+ name: 'Service',
+ value: 'service',
+ },
+ {
+ name: 'State',
+ value: 'state',
+ },
+ {
+ name: 'Template',
+ value: 'template',
+ },
+ ],
+ default: 'config',
+ description: 'Resource to consume.',
+ },
+ ...cameraProxyOperations,
+ ...cameraProxyFields,
+ ...configOperations,
+ ...eventOperations,
+ ...eventFields,
+ ...historyOperations,
+ ...historyFields,
+ ...logOperations,
+ ...logFields,
+ ...serviceOperations,
+ ...serviceFields,
+ ...stateOperations,
+ ...stateFields,
+ ...templateOperations,
+ ...templateFields,
+ ],
+ };
+
+ async execute(this: IExecuteFunctions): Promise {
+ const items = this.getInputData();
+ const returnData: IDataObject[] = [];
+ const length = items.length;
+ const resource = this.getNodeParameter('resource', 0) as string;
+ const operation = this.getNodeParameter('operation', 0) as string;
+ const qs: IDataObject = {};
+ let responseData;
+ for (let i = 0; i < length; i++) {
+ try {
+ if (resource === 'config') {
+ if (operation === 'get') {
+ responseData = await homeAssistantApiRequest.call(this, 'GET', '/config');
+ } else if (operation === 'check') {
+ responseData = await homeAssistantApiRequest.call(this, 'POST', '/config/core/check_config');
+ }
+ } else if (resource === 'service') {
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ responseData = await homeAssistantApiRequest.call(this, 'GET', '/services') as IDataObject[];
+ if (!returnAll) {
+ const limit = this.getNodeParameter('limit', i) as number;
+ responseData = responseData.slice(0, limit);
+ }
+ } else if (operation === 'call') {
+ const domain = this.getNodeParameter('domain', i) as string;
+ const service = this.getNodeParameter('service', i) as string;
+ const serviceAttributes = this.getNodeParameter('serviceAttributes', i) as {
+ attributes: IDataObject[],
+ };
+
+ const body: IDataObject = {};
+
+ if (Object.entries(serviceAttributes).length) {
+ if (serviceAttributes.attributes !== undefined) {
+ serviceAttributes.attributes.map(
+ attribute => {
+ // @ts-ignore
+ body[attribute.name as string] = attribute.value;
+ },
+ );
+ }
+ }
+
+ responseData = await homeAssistantApiRequest.call(this, 'POST', `/services/${domain}/${service}`, body);
+ if (Array.isArray(responseData) && responseData.length === 0) {
+ responseData = {};
+ }
+ }
+ } else if (resource === 'state') {
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ responseData = await homeAssistantApiRequest.call(this, 'GET', '/states') as IDataObject[];
+ if (!returnAll) {
+ const limit = this.getNodeParameter('limit', i) as number;
+ responseData = responseData.slice(0, limit);
+ }
+ } else if (operation === 'get') {
+ const entityId = this.getNodeParameter('entityId', i) as string;
+ responseData = await homeAssistantApiRequest.call(this, 'GET', `/states/${entityId}`);
+ } else if (operation === 'upsert') {
+ const entityId = this.getNodeParameter('entityId', i) as string;
+ const state = this.getNodeParameter('state', i) as string;
+ const stateAttributes = this.getNodeParameter('stateAttributes', i) as {
+ attributes: IDataObject[],
+ };
+
+ const body = {
+ state,
+ attributes: {},
+ };
+
+ if (Object.entries(stateAttributes).length) {
+ if (stateAttributes.attributes !== undefined) {
+ stateAttributes.attributes.map(
+ attribute => {
+ // @ts-ignore
+ body.attributes[attribute.name as string] = attribute.value;
+ },
+ );
+ }
+ }
+
+ responseData = await homeAssistantApiRequest.call(this, 'POST', `/states/${entityId}`, body);
+ }
+ } else if (resource === 'event') {
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ responseData = await homeAssistantApiRequest.call(this, 'GET', '/events') as IDataObject[];
+ if (!returnAll) {
+ const limit = this.getNodeParameter('limit', i) as number;
+ responseData = responseData.slice(0, limit);
+ }
+ } else if (operation === 'post') {
+ const eventType = this.getNodeParameter('eventType', i) as string;
+ const eventAttributes = this.getNodeParameter('eventAttributes', i) as {
+ attributes: IDataObject[],
+ };
+
+ const body = {};
+
+ if (Object.entries(eventAttributes).length) {
+ if (eventAttributes.attributes !== undefined) {
+ eventAttributes.attributes.map(
+ attribute => {
+ // @ts-ignore
+ body[attribute.name as string] = attribute.value;
+ },
+ );
+ }
+ }
+
+ responseData = await homeAssistantApiRequest.call(this, 'POST', `/events/${eventType}`, body);
+
+ }
+ } else if (resource === 'log') {
+ if (operation === 'getErroLogs') {
+ responseData = await homeAssistantApiRequest.call(this, 'GET', '/error_log');
+ if (responseData) {
+ responseData = {
+ errorLog: responseData,
+ };
+ }
+ } else if (operation === 'getLogbookEntries') {
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ let endpoint = '/logbook';
+
+ if (Object.entries(additionalFields).length) {
+ if (additionalFields.startTime) {
+ endpoint = `/logbook/${additionalFields.startTime}`;
+ }
+ if (additionalFields.endTime) {
+ qs.end_time = additionalFields.endTime;
+ }
+ if (additionalFields.entityId) {
+ qs.entity = additionalFields.entityId;
+ }
+ }
+
+ responseData = await homeAssistantApiRequest.call(this, 'GET', endpoint, {}, qs);
+
+ }
+ } else if (resource === 'template') {
+ if (operation === 'create') {
+ const body = {
+ template: this.getNodeParameter('template', i) as string,
+ };
+ responseData = await homeAssistantApiRequest.call(this, 'POST', '/template', body);
+ if (responseData) {
+ responseData = { renderedTemplate: responseData };
+ }
+ }
+ } else if (resource === 'history') {
+ if (operation === 'getAll') {
+ const returnAll = this.getNodeParameter('returnAll', i) as boolean;
+ const additionalFields = this.getNodeParameter('additionalFields', i) as IDataObject;
+ let endpoint = '/history/period';
+
+ if (Object.entries(additionalFields).length) {
+ if (additionalFields.startTime) {
+ endpoint = `/history/period/${additionalFields.startTime}`;
+ }
+ if (additionalFields.endTime) {
+ qs.end_time = additionalFields.endTime;
+ }
+ if (additionalFields.entityIds) {
+ qs.filter_entity_id = additionalFields.entityIds;
+ }
+ if (additionalFields.minimalResponse === true) {
+ qs.minimal_response = additionalFields.minimalResponse;
+ }
+ if (additionalFields.significantChangesOnly === true) {
+ qs.significant_changes_only = additionalFields.significantChangesOnly;
+ }
+ }
+
+ responseData = await homeAssistantApiRequest.call(this, 'GET', endpoint, {}, qs) as IDataObject[];
+ if (!returnAll) {
+ const limit = this.getNodeParameter('limit', i) as number;
+ responseData = responseData.slice(0, limit);
+ }
+ }
+ } else if (resource === 'cameraProxy') {
+ if (operation === 'getScreenshot') {
+ const cameraEntityId = this.getNodeParameter('cameraEntityId', i) as string;
+ const dataPropertyNameDownload = this.getNodeParameter('binaryPropertyName', i) as string;
+ const endpoint = `/camera_proxy/${cameraEntityId}`;
+
+ let mimeType: string | undefined;
+
+ responseData = await homeAssistantApiRequest.call(this, 'GET', endpoint, {}, {}, undefined, {
+ encoding: null,
+ resolveWithFullResponse: true,
+ });
+
+ const newItem: INodeExecutionData = {
+ json: items[i].json,
+ binary: {},
+ };
+
+ if (mimeType === undefined && responseData.headers['content-type']) {
+ mimeType = responseData.headers['content-type'];
+ }
+
+ if (items[i].binary !== undefined) {
+ // Create a shallow copy of the binary data so that the old
+ // data references which do not get changed still stay behind
+ // but the incoming data does not get changed.
+ Object.assign(newItem.binary, items[i].binary);
+ }
+
+ items[i] = newItem;
+
+ const data = Buffer.from(responseData.body as string);
+
+ items[i].binary![dataPropertyNameDownload] = await this.helpers.prepareBinaryData(data, 'screenshot.jpg', mimeType);
+ }
+ }
+ } catch (error) {
+ if (this.continueOnFail()) {
+ if (resource === 'cameraProxy' && operation === 'get') {
+ items[i].json = { error: error.message };
+ } else {
+ returnData.push({ error: error.message });
+ }
+ continue;
+ }
+ throw error;
+ }
+
+ Array.isArray(responseData)
+ ? returnData.push(...responseData)
+ : returnData.push(responseData);
+ }
+
+ if (resource === 'cameraProxy' && operation === 'getScreenshot') {
+ return this.prepareOutputData(items);
+ } else {
+ return [this.helpers.returnJsonArray(returnData)];
+ }
+ }
+}
diff --git a/packages/nodes-base/nodes/HomeAssistant/LogDescription.ts b/packages/nodes-base/nodes/HomeAssistant/LogDescription.ts
new file mode 100644
index 0000000000..ba1d5c651f
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/LogDescription.ts
@@ -0,0 +1,78 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const logOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Get Error Logs',
+ value: 'getErroLogs',
+ description: 'Get a log for a specific entity',
+ },
+ {
+ name: 'Get Logbook Entries',
+ value: 'getLogbookEntries',
+ description: 'Get all logs',
+ },
+ ],
+ default: 'getErroLogs',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const logFields = [
+ /* -------------------------------------------------------------------------- */
+ /* log:getLogbookEntries */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Additional Fields',
+ name: 'additionalFields',
+ type: 'collection',
+ placeholder: 'Add Field',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'log',
+ ],
+ operation: [
+ 'getLogbookEntries',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'End Time',
+ name: 'endTime',
+ type: 'dateTime',
+ default: '',
+ description: 'The end of the period.',
+ },
+ {
+ displayName: 'Entity ID',
+ name: 'entityId',
+ type: 'string',
+ default: '',
+ description: 'The entity ID.',
+ },
+ {
+ displayName: 'Start Time',
+ name: 'startTime',
+ type: 'dateTime',
+ default: '',
+ description: 'The beginning of the period.',
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/ServiceDescription.ts b/packages/nodes-base/nodes/HomeAssistant/ServiceDescription.ts
new file mode 100644
index 0000000000..d278d47227
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/ServiceDescription.ts
@@ -0,0 +1,159 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const serviceOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'service',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Call',
+ value: 'call',
+ description: 'Call a service within a specific domain',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all services',
+ },
+ ],
+ default: 'getAll',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const serviceFields = [
+ /* -------------------------------------------------------------------------- */
+ /* service:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'service',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'service',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 50,
+ description: 'How many results to return.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* service:Call */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Domain',
+ name: 'domain',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'service',
+ ],
+ operation: [
+ 'call',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Service',
+ name: 'service',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'service',
+ ],
+ operation: [
+ 'call',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'Service Attributes',
+ name: 'serviceAttributes',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ placeholder: 'Add Attribute',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'service',
+ ],
+ operation: [
+ 'call',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'attributes',
+ displayName: 'Attributes',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Name of the field.',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value of the field.',
+ },
+ ],
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/StateDescription.ts b/packages/nodes-base/nodes/HomeAssistant/StateDescription.ts
new file mode 100644
index 0000000000..aa356f1c7a
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/StateDescription.ts
@@ -0,0 +1,187 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const stateOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'state',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create or update',
+ value: 'upsert',
+ description: 'Create a new record, or update the current one if it already exists (upsert)',
+ },
+ {
+ name: 'Get',
+ value: 'get',
+ description: 'Get a state for a specific entity',
+ },
+ {
+ name: 'Get All',
+ value: 'getAll',
+ description: 'Get all states',
+ },
+ ],
+ default: 'get',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const stateFields = [
+ /* -------------------------------------------------------------------------- */
+ /* state:get */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Entity ID',
+ name: 'entityId',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: [
+ 'get',
+ ],
+ resource: [
+ 'state',
+ ],
+ },
+ },
+ required: true,
+ default: '',
+ description: 'The entity ID.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* state:getAll */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Return All',
+ name: 'returnAll',
+ type: 'boolean',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'state',
+ ],
+ },
+ },
+ default: false,
+ description: 'If all results should be returned or only up to a given limit.',
+ },
+ {
+ displayName: 'Limit',
+ name: 'limit',
+ type: 'number',
+ displayOptions: {
+ show: {
+ operation: [
+ 'getAll',
+ ],
+ resource: [
+ 'state',
+ ],
+ returnAll: [
+ false,
+ ],
+ },
+ },
+ typeOptions: {
+ minValue: 1,
+ maxValue: 100,
+ },
+ default: 50,
+ description: 'How many results to return.',
+ },
+
+ /* -------------------------------------------------------------------------- */
+ /* state:upsert */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Entity ID',
+ name: 'entityId',
+ type: 'string',
+ displayOptions: {
+ show: {
+ operation: [
+ 'upsert',
+ ],
+ resource: [
+ 'state',
+ ],
+ },
+ },
+ required: true,
+ default: '',
+ description: 'The entity ID for which a state will be created.',
+ },
+ {
+ displayName: 'State',
+ name: 'state',
+ type: 'string',
+ default: '',
+ required: true,
+ displayOptions: {
+ show: {
+ resource: [
+ 'state',
+ ],
+ operation: [
+ 'upsert',
+ ],
+ },
+ },
+ },
+ {
+ displayName: 'State Attributes',
+ name: 'stateAttributes',
+ type: 'fixedCollection',
+ typeOptions: {
+ multipleValues: true,
+ },
+ placeholder: 'Add Attribute',
+ default: {},
+ displayOptions: {
+ show: {
+ resource: [
+ 'state',
+ ],
+ operation: [
+ 'upsert',
+ ],
+ },
+ },
+ options: [
+ {
+ displayName: 'Attributes',
+ name: 'attributes',
+ values: [
+ {
+ displayName: 'Name',
+ name: 'name',
+ type: 'string',
+ default: '',
+ description: 'Name of the attribute.',
+ },
+ {
+ displayName: 'Value',
+ name: 'value',
+ type: 'string',
+ default: '',
+ description: 'Value of the attribute.',
+ },
+ ],
+ },
+ ],
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/TemplateDescription.ts b/packages/nodes-base/nodes/HomeAssistant/TemplateDescription.ts
new file mode 100644
index 0000000000..9f35155c6a
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/TemplateDescription.ts
@@ -0,0 +1,52 @@
+import {
+ INodeProperties
+} from 'n8n-workflow';
+
+export const templateOperations = [
+ {
+ displayName: 'Operation',
+ name: 'operation',
+ type: 'options',
+ displayOptions: {
+ show: {
+ resource: [
+ 'template',
+ ],
+ },
+ },
+ options: [
+ {
+ name: 'Create',
+ value: 'create',
+ description: 'create a template',
+ },
+ ],
+ default: 'create',
+ description: 'The operation to perform.',
+ },
+] as INodeProperties[];
+
+export const templateFields = [
+
+ /* -------------------------------------------------------------------------- */
+ /* template:create */
+ /* -------------------------------------------------------------------------- */
+ {
+ displayName: 'Template',
+ name: 'template',
+ type: 'string',
+ displayOptions: {
+ show: {
+ resource: [
+ 'template',
+ ],
+ operation: [
+ 'create',
+ ],
+ },
+ },
+ required: true,
+ default: '',
+ description: 'Render a Home Assistant template. See template docs for more information.',
+ },
+] as INodeProperties[];
diff --git a/packages/nodes-base/nodes/HomeAssistant/homeAssistant.svg b/packages/nodes-base/nodes/HomeAssistant/homeAssistant.svg
new file mode 100644
index 0000000000..9e25b23da5
--- /dev/null
+++ b/packages/nodes-base/nodes/HomeAssistant/homeAssistant.svg
@@ -0,0 +1,16 @@
+
diff --git a/packages/nodes-base/package.json b/packages/nodes-base/package.json
index 4d7363c412..d4fbd2eba1 100644
--- a/packages/nodes-base/package.json
+++ b/packages/nodes-base/package.json
@@ -119,6 +119,7 @@
"dist/credentials/HarvestApi.credentials.js",
"dist/credentials/HarvestOAuth2Api.credentials.js",
"dist/credentials/HelpScoutOAuth2Api.credentials.js",
+ "dist/credentials/HomeAssistantApi.credentials.js",
"dist/credentials/HttpBasicAuth.credentials.js",
"dist/credentials/HttpDigestAuth.credentials.js",
"dist/credentials/HttpHeaderAuth.credentials.js",
@@ -403,6 +404,7 @@
"dist/nodes/Harvest/Harvest.node.js",
"dist/nodes/HelpScout/HelpScout.node.js",
"dist/nodes/HelpScout/HelpScoutTrigger.node.js",
+ "dist/nodes/HomeAssistant/HomeAssistant.node.js",
"dist/nodes/HtmlExtract/HtmlExtract.node.js",
"dist/nodes/HttpRequest.node.js",
"dist/nodes/Hubspot/Hubspot.node.js",