node-red-contrib-dirigera
Version:
A Node-Red Node for controlling Ikea Dirigera hub.
145 lines (139 loc) • 6.13 kB
JavaScript
module.exports = function (RED) {
'use strict'
const DirigeraHub = require('node-dirigera-promise')
const handleError = (res, errorText, done) => {
if (done) return
res.json(JSON.stringify({ error: errorText }))
done = true
}
RED.httpAdmin.get('/ikeaDirigera/auth', RED.auth.needsPermission('dirigera.write'), async function (req, res) {
let done = false
const hubAddress = String(req.query.hubAddress)
if (!hubAddress) {
handleError(res, 'Hub address empty. Missing ip or hostname to use.', done)
return
}
res.setTimeout(60000, function () {
handleError(res, 'Button push was not registered at address: ' + hubAddress, done)
})
try {
const hubOptions = {
hubAddress,
clientName: req.query.clientName || 'node-red-dirigera',
debug: 0
}
const dirigeraHub = new DirigeraHub(hubOptions)
res.json(JSON.stringify({ access_token: await dirigeraHub.getAccessToken() }))
done = true
} catch (error) {
handleError(res, '/ikeaDirigera/auth > getAccessToken:err > ' + String(error), done)
}
})
RED.httpAdmin.get('/ikeaDirigera/dirigera', RED.auth.needsPermission('dirigera.read'), async function (req, res) {
const node = RED.nodes.getNode(req.query.nodeId) || {}
if (!node.dirigeraClient) {
res.json('{"Make and deploy config first":[{"name":"Make and deploy config first", "id":-1}]}')
return
}
try {
const result = { specificDevice: [] }
const devices = await node.dirigeraClient.getDevice()
for (const device of devices) {
try {
const roomName = device.room?.name || '(no room defined)'
result.specificDevice.push({ name: `${device.type || ''} - ${roomName} - ${device.attributes.customName || ''}`, id: device.id })
if (device.type === 'gateway') continue
const roomID = device.room?.id || ''
if (!result[device.type]) {
result[device.type] = [{ name: 'Override (msg.roomId)', id: 'overrideRoom' }, { name: roomName, id: roomID }]
} else if (!result[device.type].some(item => item.id === roomID)) {
result[device.type].push({ name: roomName, id: roomID })
}
} catch (error) {
console.error('/ikeaDirigera/dirigera-API-Error:', error, device)
}
}
result.specificDevice.sort((a, b) => a.name.localeCompare(b.name))
result.specificDevice.unshift({ name: 'Override (msg.deviceId)', id: 'overrideDevice' })
const scenes = await node.dirigeraClient.getScene()
result.scene = scenes.map(scene => ({ name: scene.info.name, id: scene.id }))
res.json(JSON.stringify(result))
} catch (error) {
res.json('{"Error see console":[{"name":"' + String(error) + '", "id":-1}]}')
node.error('Dirigera get devices error: ' + error)
}
})
function DirigeraConfigNode (n) {
RED.nodes.createNode(this, n)
const node = this
if (!node.credentials.hubAddress || !node.credentials.hubAccessCode) return
const dirigeraHub = new DirigeraHub({ hubAddress: node.credentials.hubAddress, access_token: node.credentials.hubAccessCode, debug: 0 })
dirigeraHub.logIn().then(() => {
node.dirigeraClient = dirigeraHub
node.lastError = ''
}).catch((error) => {
node.dirigeraClient = undefined
node.error('Dirigera config error: ' + error)
node.lastError = String(error) || ''
})
}
RED.nodes.registerType('dirigera-config', DirigeraConfigNode, {
credentials: {
hubAddress: { type: 'text' },
hubAccessCode: { type: 'text' }
}
})
function DirigeraNode (config) {
RED.nodes.createNode(this, config)
const node = this
this.config = config
node.on('input', async function (msg, send, done) {
node.server = RED.nodes.getNode(config.server)
node.status({ fill: '', text: '' })
try {
if (!node.server || !node.server.dirigeraClient) {
const lastError = node.server && node.server.lastError ? ': ' + node.server.lastError : ''
throw new Error('Unknown config error' + lastError)
}
if (node.config.choiceId === -1) {
throw new Error('Choices error in dropdowns or config error')
}
if (node.config.choiceType === 'scene') {
await node.server.dirigeraClient.triggerScene(node.config.choiceId)
} else if (node.config.choiceType === 'specificDevice') {
let deviceId = node.config.choiceId
if (node.config.choiceId === 'overrideDevice') {
if (!msg.deviceId) throw new Error('No deviceId in msg.deviceId')
deviceId = msg.deviceId
}
if (msg.topic) {
msg.payload = await node.server.dirigeraClient.setDevice(deviceId, msg.topic, msg.payload)
} else {
msg.payload = await node.server.dirigeraClient.getDevice(deviceId)
msg.availableTopics = msg.payload.capabilities.canReceive
}
send(msg)
} else {
let roomId = node.config.choiceId
if (node.config.choiceId === 'overrideRoom') {
if (!msg.roomId) throw new Error('No roomId in msg.roomId')
roomId = msg.roomId
}
if (msg.topic) {
msg.payload = await node.server.dirigeraClient.setRoom(roomId, msg.topic, msg.payload, node.config.choiceType)
if (msg.payload.ok.length === 0 && msg.payload.errors.length > 0) throw new Error(msg.payload.errors[0])
} else {
msg.payload = await node.server.dirigeraClient.getRoom(roomId, node.config.choiceType)
msg.availableTopics = msg.payload[0].capabilities.canReceive
}
send(msg)
}
done()
} catch (error) {
node.status({ fill: 'red', text: error.message || error })
done(error.message || error)
}
})
}
RED.nodes.registerType('dirigera', DirigeraNode)
}