n8n-nodes-bluelinky
Version:
n8n node (wrapper for bluelinky)
235 lines (234 loc) • 11.6 kB
JavaScript
"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.BlueLinky = void 0;
const n8n_workflow_1 = require("n8n-workflow");
const bluelinky_1 = require("bluelinky");
async function initClient() {
const credentials = await this.getCredentials('blueLinkyApi');
if (!credentials) {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), 'Missing BlueLinky credentials');
}
// @ts-ignore -- brand is missing, only required in v10 of Bluelinky
const client = new bluelinky_1.BlueLinky({
username: credentials.username,
password: credentials.password,
region: credentials.region,
language: (credentials.language || 'en'),
pin: (credentials.pin || undefined),
});
await client.login();
return client;
}
async function resolveVehicle(client, vin, index) {
const vehicles = await client.getVehicles();
if (!vehicles || vehicles.length === 0) {
throw new Error('No vehicles found for this BlueLinky account.');
}
if (vin) {
const match = vehicles.find((v) => { var _a; return v.vin === vin || ((_a = v.vehicleConfig) === null || _a === void 0 ? void 0 : _a.vin) === vin; });
if (!match) {
throw new Error(`Vehicle with VIN ${vin} not found.`);
}
return match;
}
const i = typeof index === 'number' ? index : 0;
if (i < 0 || i >= vehicles.length) {
throw new Error(`Vehicle index ${i} is out of bounds (found ${vehicles.length} vehicles).`);
}
return vehicles[i];
}
class BlueLinky {
constructor() {
this.description = {
displayName: 'BlueLinky',
name: 'blueLinky',
icon: 'file:bluelinky.svg',
group: ['transform'],
version: 1,
description: 'Control Hyundai/Kia vehicles via the bluelinky library',
defaults: {
name: 'BlueLinky',
},
inputs: ['main'],
outputs: ['main'],
credentials: [
{
name: 'blueLinkyApi',
required: true,
},
],
properties: [
{
displayName: 'Operation',
name: 'operation',
type: 'options',
noDataExpression: true,
options: [
{ name: 'List Vehicles', value: 'listVehicles', description: 'Get a list of vehicles' },
{ name: 'Get Vehicle Status', value: 'getStatus', description: 'Fetch current vehicle status' },
{ name: 'Get Location', value: 'getLocation', description: 'Fetch current vehicle location' },
{ name: 'Get Odometer', value: 'getOdometer', description: 'Fetch current vehicle odometer' },
{ name: 'Lock', value: 'lock', description: 'Lock the vehicle' },
{ name: 'Unlock', value: 'unlock', description: 'Unlock the vehicle' },
{ name: 'Start Engine/Climate', value: 'startEngine', description: 'Start engine or climate with options' },
{ name: 'Stop Engine/Climate', value: 'stopEngine', description: 'Stop engine or climate' },
],
default: 'listVehicles',
},
// Vehicle selection
{
displayName: 'VIN',
name: 'vin',
type: 'string',
default: '',
placeholder: 'Optional VIN to target a specific vehicle',
description: 'If empty, vehicle index will be used',
displayOptions: {
show: {
operation: ['getStatus', 'getLocation', 'getOdometer', 'lock', 'unlock', 'startEngine', 'stopEngine'],
},
},
},
{
displayName: 'Vehicle Index',
name: 'vehicleIndex',
type: 'number',
typeOptions: { minValue: 0 },
default: 0,
description: 'Index of the vehicle to use when VIN is not provided',
displayOptions: {
show: {
operation: ['getStatus', 'getLocation', 'getOdometer', 'lock', 'unlock', 'startEngine', 'stopEngine'],
},
},
},
// Start options
{
displayName: 'Start Options',
name: 'startOptions',
type: 'collection',
default: {},
placeholder: 'Add Option',
options: [
{
displayName: 'Air Control',
name: 'airCtrl',
type: 'boolean',
default: true,
description: 'Enable climate control',
},
{
displayName: 'Defrost',
name: 'defrost',
type: 'boolean',
default: false,
},
{
displayName: 'Heating Temperature (°C)',
name: 'heatingTemp',
type: 'number',
typeOptions: { minValue: 16, maxValue: 32 },
default: 22,
description: 'Target temperature for climate start (if supported)',
},
{
displayName: 'Duration (minutes)',
name: 'igniOnDuration',
type: 'number',
typeOptions: { minValue: 1, maxValue: 30 },
default: 10,
description: 'How long to run the engine/climate',
},
],
displayOptions: {
show: {
operation: ['startEngine'],
},
},
},
],
};
}
async execute() {
var _a, _b, _c, _d;
const returnData = [];
const operation = this.getNodeParameter('operation', 0);
const client = await initClient.call(this);
try {
if (operation === 'listVehicles') {
const vehicles = await client.getVehicles();
const data = vehicles.map((v) => {
var _a, _b, _c, _d, _e;
return ({
vin: (_a = v.vin()) !== null && _a !== void 0 ? _a : (_b = v.vehicleConfig) === null || _b === void 0 ? void 0 : _b.vin,
id: (_c = v.id()) !== null && _c !== void 0 ? _c : null,
nickname: (_e = (_d = v.nickname()) !== null && _d !== void 0 ? _d : v.name()) !== null && _e !== void 0 ? _e : null,
});
});
returnData.push(...data.map((d) => ({ json: d })));
}
else if (operation === 'getStatus') {
const vin = this.getNodeParameter('vin', 0, '');
const index = this.getNodeParameter('vehicleIndex', 0, 0);
const vehicle = await resolveVehicle(client, vin || undefined, index);
// Many setups support a status method with parsed/refresh flags
const status = (_b = await ((_a = vehicle.status) === null || _a === void 0 ? void 0 : _a.call(vehicle, { parsed: true, refresh: true }))) !== null && _b !== void 0 ? _b : await ((_c = vehicle.fullStatus) === null || _c === void 0 ? void 0 : _c.call(vehicle, { parsed: true, refresh: true }));
returnData.push({ json: status !== null && status !== void 0 ? status : { message: 'No status returned' } });
}
else if (operation === 'getLocation') {
const vin = this.getNodeParameter('vin', 0, '');
const index = this.getNodeParameter('vehicleIndex', 0, 0);
const vehicle = await resolveVehicle(client, vin || undefined, index);
const location = await ((_d = vehicle.location) === null || _d === void 0 ? void 0 : _d.call(vehicle));
returnData.push({ json: location !== null && location !== void 0 ? location : { message: 'No location returned' } });
}
else if (operation === 'getOdometer') {
const vin = this.getNodeParameter('vin', 0, '');
const index = this.getNodeParameter('vehicleIndex', 0, 0);
const vehicle = await resolveVehicle(client, vin || undefined, index);
let odo = await vehicle.odometer();
returnData.push({ json: odo !== null && odo !== void 0 ? odo : { message: 'No odometer returned' } });
}
else if (operation === 'lock') {
const vin = this.getNodeParameter('vin', 0, '');
const index = this.getNodeParameter('vehicleIndex', 0, 0);
const vehicle = await resolveVehicle(client, vin || undefined, index);
const res = await vehicle.lock();
returnData.push({ json: res !== null && res !== void 0 ? res : { success: true } });
}
else if (operation === 'unlock') {
const vin = this.getNodeParameter('vin', 0, '');
const index = this.getNodeParameter('vehicleIndex', 0, 0);
const vehicle = await resolveVehicle(client, vin || undefined, index);
const res = await vehicle.unlock();
returnData.push({ json: res !== null && res !== void 0 ? res : { success: true } });
}
else if (operation === 'startEngine') {
const vin = this.getNodeParameter('vin', 0, '');
const index = this.getNodeParameter('vehicleIndex', 0, 0);
const startOptions = this.getNodeParameter('startOptions', 0, {});
const vehicle = await resolveVehicle(client, vin || undefined, index);
const res = await vehicle.start(startOptions);
returnData.push({ json: res !== null && res !== void 0 ? res : { success: true } });
}
else if (operation === 'stopEngine') {
const vin = this.getNodeParameter('vin', 0, '');
const index = this.getNodeParameter('vehicleIndex', 0, 0);
const vehicle = await resolveVehicle(client, vin || undefined, index);
const res = await vehicle.stop();
returnData.push({ json: res !== null && res !== void 0 ? res : { success: true } });
}
else {
throw new n8n_workflow_1.NodeOperationError(this.getNode(), `Unknown operation: ${operation}`);
}
}
catch (error) {
if (error instanceof n8n_workflow_1.NodeOperationError) {
throw error;
}
throw new n8n_workflow_1.NodeOperationError(this.getNode(), error.message);
}
return [returnData];
}
}
exports.BlueLinky = BlueLinky;