first commit
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
Security: Sync from Public / sync-from-public (push) Has been cancelled
Test: Benchmark Nightly / build (push) Has been cancelled
Test: Benchmark Nightly / Notify Cats on failure (push) Has been cancelled
CI: Python / Checks (push) Has been cancelled
Test: Evals Python / Workflow Comparison Python (push) Has been cancelled
Util: Check Docs URLs / check-docs-urls (push) Has been cancelled
Test: Visual Storybook / Cloudflare Pages (push) Has been cancelled
Test: E2E Performance / build-and-test-performance (push) Has been cancelled
Test: Workflows Nightly / Run Workflow Tests (push) Has been cancelled
Util: Cleanup CI Docker Images / Delete stale CI images (push) Has been cancelled
Test: Benchmark Destroy Env / build (push) Has been cancelled
Util: Update Node Popularity / update-popularity (push) Has been cancelled
Test: E2E Coverage Weekly / Coverage Tests (push) Has been cancelled
This commit is contained in:
@@ -0,0 +1,65 @@
|
||||
import type {
|
||||
JsonObject,
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
IHttpRequestMethods,
|
||||
IRequestOptions,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeApiError } from 'n8n-workflow';
|
||||
|
||||
export async function philipsHueApiRequest(
|
||||
this: IExecuteFunctions | ILoadOptionsFunctions,
|
||||
method: IHttpRequestMethods,
|
||||
resource: string,
|
||||
|
||||
body: any = {},
|
||||
qs: IDataObject = {},
|
||||
uri?: string,
|
||||
headers: IDataObject = {},
|
||||
): Promise<any> {
|
||||
const options: IRequestOptions = {
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
method,
|
||||
body,
|
||||
qs,
|
||||
uri: uri || `https://api.meethue.com/route${resource}`,
|
||||
json: true,
|
||||
};
|
||||
try {
|
||||
if (Object.keys(headers).length !== 0) {
|
||||
options.headers = Object.assign({}, options.headers, headers);
|
||||
}
|
||||
|
||||
if (Object.keys(body as IDataObject).length === 0) {
|
||||
delete options.body;
|
||||
}
|
||||
|
||||
if (Object.keys(qs).length === 0) {
|
||||
delete options.qs;
|
||||
}
|
||||
|
||||
const response = await this.helpers.requestOAuth2.call(this, 'philipsHueOAuth2Api', options, {
|
||||
tokenType: 'Bearer',
|
||||
});
|
||||
return response;
|
||||
} catch (error) {
|
||||
throw new NodeApiError(this.getNode(), error as JsonObject);
|
||||
}
|
||||
}
|
||||
|
||||
export async function getUser(this: IExecuteFunctions | ILoadOptionsFunctions): Promise<any> {
|
||||
const { whitelist } = await philipsHueApiRequest.call(this, 'GET', '/api/0/config', {}, {});
|
||||
//check if there is a n8n user
|
||||
for (const user of Object.keys(whitelist as IDataObject)) {
|
||||
if (whitelist[user].name === 'n8n') {
|
||||
return user;
|
||||
}
|
||||
}
|
||||
// n8n user was not fount then create the user
|
||||
await philipsHueApiRequest.call(this, 'PUT', '/api/0/config', { linkbutton: true });
|
||||
const { success } = await philipsHueApiRequest.call(this, 'POST', '/api', { devicetype: 'n8n' });
|
||||
return success.username;
|
||||
}
|
||||
@@ -0,0 +1,328 @@
|
||||
import type { INodeProperties } from 'n8n-workflow';
|
||||
|
||||
export const lightOperations: INodeProperties[] = [
|
||||
{
|
||||
displayName: 'Operation',
|
||||
name: 'operation',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['light'],
|
||||
},
|
||||
},
|
||||
options: [
|
||||
{
|
||||
name: 'Delete',
|
||||
value: 'delete',
|
||||
description: 'Delete a light',
|
||||
action: 'Delete a light',
|
||||
},
|
||||
{
|
||||
name: 'Get',
|
||||
value: 'get',
|
||||
description: 'Retrieve a light',
|
||||
action: 'Get a light',
|
||||
},
|
||||
{
|
||||
name: 'Get Many',
|
||||
value: 'getAll',
|
||||
description: 'Retrieve many lights',
|
||||
action: 'Get many lights',
|
||||
},
|
||||
{
|
||||
name: 'Update',
|
||||
value: 'update',
|
||||
description: 'Update a light',
|
||||
action: 'Update a light',
|
||||
},
|
||||
],
|
||||
default: 'update',
|
||||
},
|
||||
];
|
||||
|
||||
export const lightFields: INodeProperties[] = [
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* light:delete */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Light ID',
|
||||
name: 'lightId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['delete'],
|
||||
resource: ['light'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* light:getAll */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Return All',
|
||||
name: 'returnAll',
|
||||
type: 'boolean',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['light'],
|
||||
},
|
||||
},
|
||||
default: false,
|
||||
description: 'Whether to return all results or only up to a given limit',
|
||||
},
|
||||
{
|
||||
displayName: 'Limit',
|
||||
name: 'limit',
|
||||
type: 'number',
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['getAll'],
|
||||
resource: ['light'],
|
||||
returnAll: [false],
|
||||
},
|
||||
},
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 500,
|
||||
},
|
||||
default: 100,
|
||||
description: 'Max number of results to return',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* light:get */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Light ID',
|
||||
name: 'lightId',
|
||||
type: 'string',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['get'],
|
||||
resource: ['light'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
|
||||
/* -------------------------------------------------------------------------- */
|
||||
/* light:update */
|
||||
/* -------------------------------------------------------------------------- */
|
||||
{
|
||||
displayName: 'Light Name or ID',
|
||||
name: 'lightId',
|
||||
type: 'options',
|
||||
description:
|
||||
'Choose from the list, or specify an ID using an <a href="https://docs.n8n.io/code/expressions/">expression</a>',
|
||||
typeOptions: {
|
||||
loadOptionsMethod: 'getLights',
|
||||
},
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['light'],
|
||||
},
|
||||
},
|
||||
default: '',
|
||||
},
|
||||
{
|
||||
displayName: 'On',
|
||||
name: 'on',
|
||||
type: 'boolean',
|
||||
required: true,
|
||||
displayOptions: {
|
||||
show: {
|
||||
operation: ['update'],
|
||||
resource: ['light'],
|
||||
},
|
||||
},
|
||||
default: true,
|
||||
// eslint-disable-next-line n8n-nodes-base/node-param-description-boolean-without-whether
|
||||
description: 'On/Off state of the light',
|
||||
},
|
||||
{
|
||||
displayName: 'Additional Fields',
|
||||
name: 'additionalFields',
|
||||
type: 'collection',
|
||||
placeholder: 'Add Field',
|
||||
displayOptions: {
|
||||
show: {
|
||||
resource: ['light'],
|
||||
operation: ['update'],
|
||||
},
|
||||
},
|
||||
default: {},
|
||||
options: [
|
||||
{
|
||||
displayName: 'Alert Effect',
|
||||
name: 'alert',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
description: 'The light is not performing an alert effect',
|
||||
},
|
||||
{
|
||||
name: 'Select',
|
||||
value: 'select',
|
||||
description: 'The light is performing one breathe cycle',
|
||||
},
|
||||
{
|
||||
name: 'LSelect',
|
||||
value: 'lselect',
|
||||
description:
|
||||
'The light is performing breathe cycles for 15 seconds or until an "alert": "none" command is received',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The alert effect, is a temporary change to the bulb’s state',
|
||||
},
|
||||
{
|
||||
displayName: 'Brightness',
|
||||
name: 'bri',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 1,
|
||||
maxValue: 254,
|
||||
},
|
||||
default: 100,
|
||||
description:
|
||||
'The brightness value to set the light to. Brightness is a scale from 1 (the minimum the light is capable of) to 254 (the maximum).',
|
||||
},
|
||||
{
|
||||
displayName: 'Brightness Increments',
|
||||
name: 'bri_inc',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: -254,
|
||||
maxValue: 254,
|
||||
},
|
||||
default: 0,
|
||||
description:
|
||||
'Increments or decrements the value of the brightness. This value is ignored if the Brightness attribute is provided.',
|
||||
},
|
||||
{
|
||||
displayName: 'Color Temperature',
|
||||
name: 'ct',
|
||||
type: 'number',
|
||||
default: 0,
|
||||
description:
|
||||
'The Mired color temperature of the light. 2012 connected lights are capable of 153 (6500K) to 500 (2000K).',
|
||||
},
|
||||
{
|
||||
displayName: 'Color Temperature Increments',
|
||||
name: 'ct_inc',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: -65534,
|
||||
maxValue: 65534,
|
||||
},
|
||||
default: 0,
|
||||
description:
|
||||
'Increments or decrements the value of the ct. ct_inc is ignored if the ct attribute is provided.',
|
||||
},
|
||||
{
|
||||
displayName: 'Coordinates',
|
||||
name: 'xy',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '0.64394,0.33069',
|
||||
description:
|
||||
'The x and y coordinates of a color in CIE color space. The first entry is the x coordinate and the second entry is the y coordinate. Both x and y are between 0 and 1',
|
||||
},
|
||||
{
|
||||
displayName: 'Coordinates Increments',
|
||||
name: 'xy_inc',
|
||||
type: 'string',
|
||||
default: '',
|
||||
placeholder: '0.5,0.5',
|
||||
description:
|
||||
'Increments or decrements the value of the xy. This value is ignored if the Coordinates attribute is provided. Any ongoing color transition is stopped. Max value [0.5, 0.5]',
|
||||
},
|
||||
{
|
||||
displayName: 'Dynamic Effect',
|
||||
name: 'effect',
|
||||
type: 'options',
|
||||
options: [
|
||||
{
|
||||
name: 'None',
|
||||
value: 'none',
|
||||
},
|
||||
{
|
||||
name: 'Color Loop',
|
||||
value: 'colorloop',
|
||||
},
|
||||
],
|
||||
default: '',
|
||||
description: 'The dynamic effect of the light',
|
||||
},
|
||||
{
|
||||
displayName: 'Hue',
|
||||
name: 'hue',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 65535,
|
||||
},
|
||||
default: 0,
|
||||
description:
|
||||
'The hue value to set light to.The hue value is a wrapping value between 0 and 65535. Both 0 and 65535 are red, 25500 is green and 46920 is blue.',
|
||||
},
|
||||
{
|
||||
displayName: 'Hue Increments',
|
||||
name: 'hue_inc',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: -65534,
|
||||
maxValue: 65534,
|
||||
},
|
||||
default: 0,
|
||||
description:
|
||||
'Increments or decrements the value of the hue. Hue Increments is ignored if the Hue attribute is provided.',
|
||||
},
|
||||
{
|
||||
displayName: 'Saturation',
|
||||
name: 'sat',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: 0,
|
||||
maxValue: 254,
|
||||
},
|
||||
default: 0,
|
||||
description:
|
||||
'Saturation of the light. 254 is the most saturated (colored) and 0 is the least saturated (white).',
|
||||
},
|
||||
{
|
||||
displayName: 'Saturation Increments',
|
||||
name: 'sat_inc',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minValue: -254,
|
||||
maxValue: 254,
|
||||
},
|
||||
default: 0,
|
||||
description:
|
||||
'Increments or decrements the value of the sat. This value is ignored if the Saturation attribute is provided.',
|
||||
},
|
||||
{
|
||||
displayName: 'Transition Time',
|
||||
name: 'transitiontime',
|
||||
type: 'number',
|
||||
typeOptions: {
|
||||
minVale: 1,
|
||||
},
|
||||
default: 4,
|
||||
description:
|
||||
'The duration in seconds of the transition from the light’s current state to the new state',
|
||||
},
|
||||
],
|
||||
},
|
||||
];
|
||||
@@ -0,0 +1,18 @@
|
||||
{
|
||||
"node": "n8n-nodes-base.philipsHue",
|
||||
"nodeVersion": "1.0",
|
||||
"codexVersion": "1.0",
|
||||
"categories": ["Miscellaneous"],
|
||||
"resources": {
|
||||
"credentialDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/credentials/philipshue/"
|
||||
}
|
||||
],
|
||||
"primaryDocumentation": [
|
||||
{
|
||||
"url": "https://docs.n8n.io/integrations/builtin/app-nodes/n8n-nodes-base.philipshue/"
|
||||
}
|
||||
]
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,187 @@
|
||||
import type {
|
||||
IDataObject,
|
||||
IExecuteFunctions,
|
||||
ILoadOptionsFunctions,
|
||||
INodeExecutionData,
|
||||
INodePropertyOptions,
|
||||
INodeType,
|
||||
INodeTypeDescription,
|
||||
} from 'n8n-workflow';
|
||||
import { NodeConnectionTypes } from 'n8n-workflow';
|
||||
|
||||
import { getUser, philipsHueApiRequest } from './GenericFunctions';
|
||||
import { lightFields, lightOperations } from './LightDescription';
|
||||
|
||||
export class PhilipsHue implements INodeType {
|
||||
description: INodeTypeDescription = {
|
||||
displayName: 'Philips Hue',
|
||||
name: 'philipsHue',
|
||||
// eslint-disable-next-line n8n-nodes-base/node-class-description-icon-not-svg
|
||||
icon: 'file:philipshue.png',
|
||||
group: ['input'],
|
||||
version: 1,
|
||||
subtitle: '={{$parameter["operation"] + ": " + $parameter["resource"]}}',
|
||||
description: 'Consume Philips Hue API',
|
||||
defaults: {
|
||||
name: 'Philips Hue',
|
||||
},
|
||||
usableAsTool: true,
|
||||
inputs: [NodeConnectionTypes.Main],
|
||||
outputs: [NodeConnectionTypes.Main],
|
||||
credentials: [
|
||||
{
|
||||
name: 'philipsHueOAuth2Api',
|
||||
required: true,
|
||||
},
|
||||
],
|
||||
properties: [
|
||||
{
|
||||
displayName: 'Resource',
|
||||
name: 'resource',
|
||||
type: 'options',
|
||||
noDataExpression: true,
|
||||
options: [
|
||||
{
|
||||
name: 'Light',
|
||||
value: 'light',
|
||||
},
|
||||
],
|
||||
default: 'light',
|
||||
},
|
||||
...lightOperations,
|
||||
...lightFields,
|
||||
],
|
||||
};
|
||||
|
||||
methods = {
|
||||
loadOptions: {
|
||||
// Get all the lights to display them to user so that they can
|
||||
// select them easily
|
||||
async getLights(this: ILoadOptionsFunctions): Promise<INodePropertyOptions[]> {
|
||||
const returnData: INodePropertyOptions[] = [];
|
||||
|
||||
const user = await getUser.call(this);
|
||||
|
||||
const lights = await philipsHueApiRequest.call(this, 'GET', `/api/${user}/lights`);
|
||||
|
||||
const groups = await philipsHueApiRequest.call(this, 'GET', `/api/${user}/groups`);
|
||||
|
||||
for (const light of Object.keys(lights as IDataObject)) {
|
||||
let lightName = lights[light].name;
|
||||
const lightId = light;
|
||||
|
||||
for (const groupId of Object.keys(groups as IDataObject)) {
|
||||
if (groups[groupId].type === 'Room' && groups[groupId].lights.includes(lightId)) {
|
||||
lightName = `${groups[groupId].name}: ${lightName}`;
|
||||
}
|
||||
}
|
||||
|
||||
returnData.push({
|
||||
name: lightName,
|
||||
value: lightId,
|
||||
});
|
||||
}
|
||||
return returnData;
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
async execute(this: IExecuteFunctions): Promise<INodeExecutionData[][]> {
|
||||
const items = this.getInputData();
|
||||
const returnData: INodeExecutionData[] = [];
|
||||
const length = items.length;
|
||||
let responseData;
|
||||
const resource = this.getNodeParameter('resource', 0);
|
||||
const operation = this.getNodeParameter('operation', 0);
|
||||
for (let i = 0; i < length; i++) {
|
||||
if (resource === 'light') {
|
||||
if (operation === 'update') {
|
||||
const lightId = this.getNodeParameter('lightId', i) as string;
|
||||
|
||||
const on = this.getNodeParameter('on', i) as boolean;
|
||||
|
||||
const additionalFields = this.getNodeParameter('additionalFields', i);
|
||||
|
||||
const body = {
|
||||
on,
|
||||
};
|
||||
|
||||
if (additionalFields.transitiontime) {
|
||||
additionalFields.transitiontime = (additionalFields.transitiontime as number) * 100;
|
||||
}
|
||||
|
||||
if (additionalFields.xy) {
|
||||
additionalFields.xy = (additionalFields.xy as string)
|
||||
.split(',')
|
||||
.map((e: string) => parseFloat(e));
|
||||
}
|
||||
|
||||
if (additionalFields.xy_inc) {
|
||||
additionalFields.xy_inc = (additionalFields.xy_inc as string)
|
||||
.split(',')
|
||||
.map((e: string) => parseFloat(e));
|
||||
}
|
||||
|
||||
Object.assign(body, additionalFields);
|
||||
|
||||
const user = await getUser.call(this);
|
||||
|
||||
const data = await philipsHueApiRequest.call(
|
||||
this,
|
||||
'PUT',
|
||||
`/api/${user}/lights/${lightId}/state`,
|
||||
body,
|
||||
);
|
||||
|
||||
responseData = {};
|
||||
|
||||
for (const response of data) {
|
||||
Object.assign(responseData, response.success);
|
||||
}
|
||||
}
|
||||
if (operation === 'delete') {
|
||||
const lightId = this.getNodeParameter('lightId', i) as string;
|
||||
|
||||
const user = await getUser.call(this);
|
||||
|
||||
responseData = await philipsHueApiRequest.call(
|
||||
this,
|
||||
'DELETE',
|
||||
`/api/${user}/lights/${lightId}`,
|
||||
);
|
||||
}
|
||||
if (operation === 'getAll') {
|
||||
const returnAll = this.getNodeParameter('returnAll', i);
|
||||
|
||||
const user = await getUser.call(this);
|
||||
|
||||
const lights = await philipsHueApiRequest.call(this, 'GET', `/api/${user}/lights`);
|
||||
|
||||
responseData = Object.values(lights as IDataObject);
|
||||
|
||||
if (!returnAll) {
|
||||
const limit = this.getNodeParameter('limit', i);
|
||||
responseData = responseData.splice(0, limit);
|
||||
}
|
||||
}
|
||||
if (operation === 'get') {
|
||||
const lightId = this.getNodeParameter('lightId', i) as string;
|
||||
|
||||
const user = await getUser.call(this);
|
||||
|
||||
responseData = await philipsHueApiRequest.call(
|
||||
this,
|
||||
'GET',
|
||||
`/api/${user}/lights/${lightId}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
const executionData = this.helpers.constructExecutionMetaData(
|
||||
this.helpers.returnJsonArray(responseData as IDataObject[]),
|
||||
{ itemData: { item: i } },
|
||||
);
|
||||
returnData.push(...executionData);
|
||||
}
|
||||
return [returnData];
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,163 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"capabilities": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"certified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"control": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorgamut": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"colorgamuttype": {
|
||||
"type": "string"
|
||||
},
|
||||
"ct": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer"
|
||||
},
|
||||
"min": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxlumen": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mindimlevel": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"proxy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"renderer": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"archetype": {
|
||||
"type": "string"
|
||||
},
|
||||
"direction": {
|
||||
"type": "string"
|
||||
},
|
||||
"function": {
|
||||
"type": "string"
|
||||
},
|
||||
"startup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"manufacturername": {
|
||||
"type": "string"
|
||||
},
|
||||
"modelid": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"productid": {
|
||||
"type": "string"
|
||||
},
|
||||
"productname": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alert": {
|
||||
"type": "string"
|
||||
},
|
||||
"bri": {
|
||||
"type": "integer"
|
||||
},
|
||||
"colormode": {
|
||||
"type": "string"
|
||||
},
|
||||
"ct": {
|
||||
"type": "integer"
|
||||
},
|
||||
"effect": {
|
||||
"type": "string"
|
||||
},
|
||||
"hue": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"on": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reachable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sat": {
|
||||
"type": "integer"
|
||||
},
|
||||
"xy": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"swconfigid": {
|
||||
"type": "string"
|
||||
},
|
||||
"swupdate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"lastinstall": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swversion": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,160 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"capabilities": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"certified": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"control": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"colorgamut": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
},
|
||||
"colorgamuttype": {
|
||||
"type": "string"
|
||||
},
|
||||
"ct": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"max": {
|
||||
"type": "integer"
|
||||
},
|
||||
"min": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"maxlumen": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mindimlevel": {
|
||||
"type": "integer"
|
||||
}
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"proxy": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"renderer": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"archetype": {
|
||||
"type": "string"
|
||||
},
|
||||
"direction": {
|
||||
"type": "string"
|
||||
},
|
||||
"function": {
|
||||
"type": "string"
|
||||
},
|
||||
"startup": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"configured": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"manufacturername": {
|
||||
"type": "string"
|
||||
},
|
||||
"modelid": {
|
||||
"type": "string"
|
||||
},
|
||||
"name": {
|
||||
"type": "string"
|
||||
},
|
||||
"productid": {
|
||||
"type": "string"
|
||||
},
|
||||
"productname": {
|
||||
"type": "string"
|
||||
},
|
||||
"state": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"alert": {
|
||||
"type": "string"
|
||||
},
|
||||
"bri": {
|
||||
"type": "integer"
|
||||
},
|
||||
"colormode": {
|
||||
"type": "string"
|
||||
},
|
||||
"ct": {
|
||||
"type": "integer"
|
||||
},
|
||||
"effect": {
|
||||
"type": "string"
|
||||
},
|
||||
"hue": {
|
||||
"type": "integer"
|
||||
},
|
||||
"mode": {
|
||||
"type": "string"
|
||||
},
|
||||
"on": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"reachable": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"sat": {
|
||||
"type": "integer"
|
||||
},
|
||||
"xy": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "number"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"swconfigid": {
|
||||
"type": "string"
|
||||
},
|
||||
"swupdate": {
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"state": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"swversion": {
|
||||
"type": "string"
|
||||
},
|
||||
"type": {
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueid": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"version": 1
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"/lights/3/state/on": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 2.6 KiB |
@@ -0,0 +1,108 @@
|
||||
export const getLightsResponse = {
|
||||
'1': {
|
||||
state: {
|
||||
on: false,
|
||||
bri: 1,
|
||||
hue: 33761,
|
||||
sat: 254,
|
||||
effect: 'none',
|
||||
xy: [0.3171, 0.3366],
|
||||
ct: 159,
|
||||
alert: 'none',
|
||||
colormode: 'xy',
|
||||
mode: 'homeautomation',
|
||||
reachable: true,
|
||||
},
|
||||
swupdate: {
|
||||
state: 'noupdates',
|
||||
lastinstall: '2018-01-02T19:24:20',
|
||||
},
|
||||
type: 'Extended color light',
|
||||
name: 'Hue color lamp 7',
|
||||
modelid: 'LCT007',
|
||||
manufacturername: 'Philips',
|
||||
productname: 'Hue color lamp',
|
||||
capabilities: {
|
||||
certified: true,
|
||||
control: {
|
||||
mindimlevel: 5000,
|
||||
maxlumen: 600,
|
||||
colorgamuttype: 'B',
|
||||
colorgamut: [
|
||||
[0.675, 0.322],
|
||||
[0.409, 0.518],
|
||||
[0.167, 0.04],
|
||||
],
|
||||
ct: {
|
||||
min: 153,
|
||||
max: 500,
|
||||
},
|
||||
},
|
||||
streaming: {
|
||||
renderer: true,
|
||||
proxy: false,
|
||||
},
|
||||
},
|
||||
config: {
|
||||
archetype: 'sultanbulb',
|
||||
function: 'mixed',
|
||||
direction: 'omnidirectional',
|
||||
},
|
||||
uniqueid: '00:17:88:01:00:bd:c7:b9-0b',
|
||||
swversion: '5.105.0.21169',
|
||||
},
|
||||
};
|
||||
|
||||
export const getConfigResponse = {
|
||||
name: 'Philips hue',
|
||||
zigbeechannel: 15,
|
||||
mac: '00:17:88:00:00:00',
|
||||
dhcp: true,
|
||||
ipaddress: '192.168.1.7',
|
||||
netmask: '255.255.255.0',
|
||||
gateway: '192.168.1.1',
|
||||
proxyaddress: 'none',
|
||||
proxyport: 0,
|
||||
UTC: '2014-07-17T09:27:35',
|
||||
localtime: '2014-07-17T11:27:35',
|
||||
timezone: 'Europe/Madrid',
|
||||
whitelist: {
|
||||
ffffffffe0341b1b376a2389376a2389: {
|
||||
'last use date': '2014-07-17T07:21:38',
|
||||
'create date': '2014-04-08T08:55:10',
|
||||
name: 'PhilipsHueAndroidApp#TCT ALCATEL ONE TOU',
|
||||
},
|
||||
pAtwdCV8NZId25Gk: {
|
||||
'last use date': '2014-05-07T18:28:29',
|
||||
'create date': '2014-04-09T17:29:16',
|
||||
name: 'n8n',
|
||||
},
|
||||
},
|
||||
swversion: '01012917',
|
||||
apiversion: '1.3.0',
|
||||
swupdate: {
|
||||
updatestate: 0,
|
||||
url: '',
|
||||
text: '',
|
||||
notify: false,
|
||||
},
|
||||
linkbutton: false,
|
||||
portalservices: false,
|
||||
portalconnection: 'connected',
|
||||
portalstate: {
|
||||
signedon: true,
|
||||
incoming: false,
|
||||
outgoing: true,
|
||||
communication: 'disconnected',
|
||||
},
|
||||
};
|
||||
|
||||
export const updateLightResponse = [
|
||||
{ success: { '/lights/1/state/bri': 200 } },
|
||||
{ success: { '/lights/1/state/on': true } },
|
||||
{ success: { '/lights/1/state/hue': 50000 } },
|
||||
];
|
||||
|
||||
export const deleteLightResponse = {
|
||||
success: '/lights/1 deleted.',
|
||||
};
|
||||
@@ -0,0 +1,227 @@
|
||||
{
|
||||
"name": "[TEST] Hue",
|
||||
"nodes": [
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.manualTrigger",
|
||||
"typeVersion": 1,
|
||||
"position": [0, 0],
|
||||
"id": "9e687c5d-e3a5-436d-a96d-7bfde1110f40",
|
||||
"name": "When clicking ‘Execute workflow’"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "getAll",
|
||||
"returnAll": true
|
||||
},
|
||||
"type": "n8n-nodes-base.philipsHue",
|
||||
"typeVersion": 1,
|
||||
"position": [260, 0],
|
||||
"id": "063bcf31-3055-42f1-904f-d517f85db182",
|
||||
"name": "Philips Hue",
|
||||
"credentials": {
|
||||
"philipsHueOAuth2Api": {
|
||||
"id": "s6tRicNTKQoa9TDq",
|
||||
"name": "PhilipHue account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [480, 0],
|
||||
"id": "d6c4f99a-d660-4113-aa08-a7bcc9391236",
|
||||
"name": "No Operation, do nothing"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"lightId": "=1",
|
||||
"additionalFields": {
|
||||
"bri": 200,
|
||||
"hue": 50000
|
||||
}
|
||||
},
|
||||
"type": "n8n-nodes-base.philipsHue",
|
||||
"typeVersion": 1,
|
||||
"position": [260, 200],
|
||||
"id": "c8311112-d24e-4787-80b1-cc5397aea98e",
|
||||
"name": "Philips Hue1",
|
||||
"credentials": {
|
||||
"philipsHueOAuth2Api": {
|
||||
"id": "s6tRicNTKQoa9TDq",
|
||||
"name": "PhilipHue account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [480, 200],
|
||||
"id": "c438bf28-0cf8-459a-bd3a-28f6c30b3f2f",
|
||||
"name": "No Operation, do nothing1"
|
||||
},
|
||||
{
|
||||
"parameters": {
|
||||
"operation": "delete",
|
||||
"lightId": "=1"
|
||||
},
|
||||
"type": "n8n-nodes-base.philipsHue",
|
||||
"typeVersion": 1,
|
||||
"position": [280, 400],
|
||||
"id": "fd197627-9221-4e6d-ab51-53d262004db8",
|
||||
"name": "Philips Hue2",
|
||||
"credentials": {
|
||||
"philipsHueOAuth2Api": {
|
||||
"id": "s6tRicNTKQoa9TDq",
|
||||
"name": "PhilipHue account"
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"parameters": {},
|
||||
"type": "n8n-nodes-base.noOp",
|
||||
"typeVersion": 1,
|
||||
"position": [500, 400],
|
||||
"id": "19796164-8527-462b-b01b-71d7f60c94af",
|
||||
"name": "No Operation, do nothing2"
|
||||
}
|
||||
],
|
||||
"pinData": {
|
||||
"No Operation, do nothing": [
|
||||
{
|
||||
"json": {
|
||||
"state": {
|
||||
"on": false,
|
||||
"bri": 1,
|
||||
"hue": 33761,
|
||||
"sat": 254,
|
||||
"effect": "none",
|
||||
"xy": [0.3171, 0.3366],
|
||||
"ct": 159,
|
||||
"alert": "none",
|
||||
"colormode": "xy",
|
||||
"mode": "homeautomation",
|
||||
"reachable": true
|
||||
},
|
||||
"swupdate": {
|
||||
"state": "noupdates",
|
||||
"lastinstall": "2018-01-02T19:24:20"
|
||||
},
|
||||
"type": "Extended color light",
|
||||
"name": "Hue color lamp 7",
|
||||
"modelid": "LCT007",
|
||||
"manufacturername": "Philips",
|
||||
"productname": "Hue color lamp",
|
||||
"capabilities": {
|
||||
"certified": true,
|
||||
"control": {
|
||||
"mindimlevel": 5000,
|
||||
"maxlumen": 600,
|
||||
"colorgamuttype": "B",
|
||||
"colorgamut": [[0.675, 0.322], [0.409, 0.518], [0.167, 0.04]],
|
||||
"ct": {
|
||||
"min": 153,
|
||||
"max": 500
|
||||
}
|
||||
},
|
||||
"streaming": {
|
||||
"renderer": true,
|
||||
"proxy": false
|
||||
}
|
||||
},
|
||||
"config": {
|
||||
"archetype": "sultanbulb",
|
||||
"function": "mixed",
|
||||
"direction": "omnidirectional"
|
||||
},
|
||||
"uniqueid": "00:17:88:01:00:bd:c7:b9-0b",
|
||||
"swversion": "5.105.0.21169"
|
||||
}
|
||||
}
|
||||
],
|
||||
"No Operation, do nothing1": [
|
||||
{
|
||||
"json": {
|
||||
"/lights/1/state/bri": 200,
|
||||
"/lights/1/state/on": true,
|
||||
"/lights/1/state/hue": 50000
|
||||
}
|
||||
}
|
||||
],
|
||||
"No Operation, do nothing2": [
|
||||
{
|
||||
"json": {
|
||||
"success": "/lights/1 deleted."
|
||||
}
|
||||
}
|
||||
]
|
||||
},
|
||||
"connections": {
|
||||
"When clicking ‘Execute workflow’": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "Philips Hue",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Philips Hue1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
},
|
||||
{
|
||||
"node": "Philips Hue2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Philips Hue": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Philips Hue1": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing1",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
},
|
||||
"Philips Hue2": {
|
||||
"main": [
|
||||
[
|
||||
{
|
||||
"node": "No Operation, do nothing2",
|
||||
"type": "main",
|
||||
"index": 0
|
||||
}
|
||||
]
|
||||
]
|
||||
}
|
||||
},
|
||||
"active": false,
|
||||
"settings": {
|
||||
"executionOrder": "v1"
|
||||
},
|
||||
"versionId": "2c19891b-ba8e-4026-b8c2-b5f3f508b54a",
|
||||
"meta": {
|
||||
"instanceId": "0fa937d34dcabeff4bd6480d3b42cc95edf3bc20e6810819086ef1ce2623639d"
|
||||
},
|
||||
"id": "LAngRPRzm3OGrfh0",
|
||||
"tags": []
|
||||
}
|
||||
@@ -0,0 +1,42 @@
|
||||
import { NodeTestHarness } from '@nodes-testing/node-test-harness';
|
||||
import nock from 'nock';
|
||||
|
||||
import {
|
||||
deleteLightResponse,
|
||||
getLightsResponse,
|
||||
getConfigResponse,
|
||||
updateLightResponse,
|
||||
} from './apiResponses';
|
||||
|
||||
describe('PhilipsHue', () => {
|
||||
const credentials = {
|
||||
philipsHueOAuth2Api: {
|
||||
grantType: 'authorizationCode',
|
||||
appId: 'APPID',
|
||||
authUrl: 'https://api.meethue.com/v2/oauth2/authorize',
|
||||
accessTokenUrl: 'https://api.meethue.com/v2/oauth2/token',
|
||||
authQueryParameters: 'appid=APPID',
|
||||
scope: '',
|
||||
authentication: 'header',
|
||||
oauthTokenData: {
|
||||
access_token: 'ACCESSTOKEN',
|
||||
refresh_token: 'REFRESHTOKEN',
|
||||
scope: '',
|
||||
token_type: 'bearer',
|
||||
expires_in: 86400,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
describe('Run workflow', () => {
|
||||
beforeAll(() => {
|
||||
const mock = nock('https://api.meethue.com/route');
|
||||
mock.persist().get('/api/0/config').reply(200, getConfigResponse);
|
||||
mock.get('/api/pAtwdCV8NZId25Gk/lights').reply(200, getLightsResponse);
|
||||
mock.put('/api/pAtwdCV8NZId25Gk/lights/1/state').reply(200, updateLightResponse);
|
||||
mock.delete('/api/pAtwdCV8NZId25Gk/lights/1').reply(200, deleteLightResponse);
|
||||
});
|
||||
|
||||
new NodeTestHarness().setupTests({ credentials });
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user