mirror of
https://github.com/lehanspb/tuya-mqtt.git
synced 2025-12-18 00:10:20 +00:00
3.0.0-beta2
* Implement basic template model * Generic device can specify template in devices.conf * Simple switch, dimmer, and RGBTW specific support files
This commit is contained in:
30
devices/generic-device.js
Normal file
30
devices/generic-device.js
Normal file
@@ -0,0 +1,30 @@
|
||||
const TuyaDevice = require('./tuya-device')
|
||||
const debug = require('debug')('tuya-mqtt:tuya')
|
||||
const utils = require('../lib/utils')
|
||||
|
||||
class GenericDevice extends TuyaDevice {
|
||||
async init() {
|
||||
this.deviceData.mdl = 'Generic Device'
|
||||
|
||||
// Check if custom template in device config
|
||||
if (this.config.hasOwnProperty('template')) {
|
||||
// Map generic DPS topics to device specific topic names
|
||||
this.deviceTopics = this.config.template
|
||||
console.log(this.deviceTopics)
|
||||
} else {
|
||||
// Try to get schema to at least know what DPS keys to get initial update
|
||||
const result = await this.device.get({"schema": true})
|
||||
if (!utils.isJsonString(result)) {
|
||||
if (result === 'Schema for device not available') {
|
||||
debug('Device id '+this.config.id+' failed schema discovery and no custom template defined')
|
||||
debug('Cannot get initial DPS state data for device '+this.options.name+' but data updates will be publish')
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Get initial states and start publishing topics
|
||||
this.getStates()
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = GenericDevice
|
||||
80
devices/rgbtw-light.js
Normal file
80
devices/rgbtw-light.js
Normal file
@@ -0,0 +1,80 @@
|
||||
const TuyaDevice = require('./tuya-device')
|
||||
const debug = require('debug')('tuya-mqtt:tuya')
|
||||
const utils = require('../lib/utils')
|
||||
|
||||
class RGBTWLight extends TuyaDevice {
|
||||
async init() {
|
||||
// Set device specific variables
|
||||
this.config.dpsPower = this.config.dpsPower ? this.config.dpsPower : 1
|
||||
this.config.dpsMode = this.config.dpsMode ? this.config.dpsMode : 2
|
||||
this.config.dpsWhiteValue = this.config.dpsWhiteValue ? this.config.dpsWhiteValue : 3
|
||||
this.config.whiteValueScale = this.config.whiteValueScale ? this.config.whiteValueScale : 1000
|
||||
this.config.dpsColorTemp = this.config.dpsColorTemp ? this.config.dpsColorTemp : 4
|
||||
this.config.dpsColor = this.config.dpsColor ? this.config.dpsColor : 5
|
||||
this.config.colorType = this.config.colorType ? this.config.colorType : 'hsb'
|
||||
|
||||
this.deviceData.mdl = 'RGBTW Light'
|
||||
|
||||
// Map generic DPS topics to device specific topic names
|
||||
this.deviceTopics = {
|
||||
state: {
|
||||
key: this.config.dpsPower,
|
||||
type: 'bool'
|
||||
},
|
||||
white_value_state: {
|
||||
key: this.config.dpsWhiteValue,
|
||||
type: 'int',
|
||||
min: (this.config.whiteValueScale = 1000) ? 10 : 1,
|
||||
max: this.config.whiteValueScale,
|
||||
scale: this.config.whiteValueScale
|
||||
},
|
||||
hs_state: {
|
||||
key: this.config.dpsColor,
|
||||
type: this.config.colorType,
|
||||
components: 'h,s'
|
||||
},
|
||||
brightness_state: {
|
||||
key: this.config.dpsColor,
|
||||
type: this.config.colorType,
|
||||
components: 'b'
|
||||
},
|
||||
mode_state: {
|
||||
key: this.config.dpsMode,
|
||||
type: 'str'
|
||||
}
|
||||
}
|
||||
|
||||
// Send home assistant discovery data and give it a second before sending state updates
|
||||
this.initDiscovery()
|
||||
await utils.sleep(1)
|
||||
|
||||
// Get initial states and start publishing topics
|
||||
this.getStates()
|
||||
}
|
||||
|
||||
initDiscovery() {
|
||||
const configTopic = 'homeassistant/light/'+this.config.id+'/config'
|
||||
|
||||
const discoveryData = {
|
||||
name: (this.config.name) ? this.config.name : this.config.id,
|
||||
state_topic: this.baseTopic+'state',
|
||||
command_topic: this.baseTopic+'command',
|
||||
brightness_state_topic: this.baseTopic+'brightness_state',
|
||||
brightness_command_topic: this.baseTopic+'brightness_command',
|
||||
brightness_scale: 1000,
|
||||
hs_state_topic: this.baseTopic+'hs_state',
|
||||
hs_command_topic: this.baseTopic+'hs_command',
|
||||
white_value_state_topic: this.baseTopic+'white_value_state',
|
||||
white_value_command_topic: this.baseTopic+'white_value_command',
|
||||
white_value_scale: 1000,
|
||||
unique_id: this.config.id,
|
||||
device: this.deviceData
|
||||
}
|
||||
|
||||
debug('Home Assistant config topic: '+configTopic)
|
||||
debug(discoveryData)
|
||||
this.publishMqtt(configTopic, JSON.stringify(discoveryData))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = RGBTWLight
|
||||
56
devices/simple-dimmer.js
Normal file
56
devices/simple-dimmer.js
Normal file
@@ -0,0 +1,56 @@
|
||||
const TuyaDevice = require('./tuya-device')
|
||||
const debug = require('debug')('tuya-mqtt:tuya')
|
||||
const utils = require('../lib/utils')
|
||||
|
||||
class SimpleDimmer extends TuyaDevice {
|
||||
async init() {
|
||||
// Set device specific variables
|
||||
this.config.dpsPower = this.config.dpsPower ? this.config.dpsPower : 1
|
||||
this.config.dpsBrightness = this.config.dpsBrightness ? this.config.dpsBrightness : 2
|
||||
this.config.brightnessScale = this.config.brightnessScale ? this.config.brightnessScale : 255
|
||||
|
||||
this.deviceData.mdl = 'Dimmer Switch'
|
||||
|
||||
// Map generic DPS topics to device specific topic names
|
||||
this.deviceTopics = {
|
||||
state: {
|
||||
key: this.config.dpsPower,
|
||||
type: 'bool'
|
||||
},
|
||||
brightness_state: {
|
||||
key: this.config.dpsBrightness,
|
||||
type: 'int',
|
||||
min: (this.config.brightnessScale = 1000) ? 10 : 1,
|
||||
max: this.config.brightnessScale,
|
||||
scale: this.config.brightnessScale
|
||||
}
|
||||
}
|
||||
|
||||
// Send home assistant discovery data and give it a second before sending state updates
|
||||
this.initDiscovery()
|
||||
await utils.sleep(1)
|
||||
|
||||
// Get initial states and start publishing topics
|
||||
this.getStates()
|
||||
}
|
||||
|
||||
initDiscovery() {
|
||||
const configTopic = 'homeassistant/light/'+this.config.id+'/config'
|
||||
|
||||
const discoveryData = {
|
||||
name: (this.config.name) ? this.config.name : this.config.id,
|
||||
state_topic: this.baseTopic+'state',
|
||||
command_topic: this.baseTopic+'command',
|
||||
brightness_state_topic: this.baseTopic+'brightness_state',
|
||||
brightness_command_topic: this.baseTopic+'brightness_command',
|
||||
unique_id: this.config.id,
|
||||
device: this.deviceData
|
||||
}
|
||||
|
||||
debug('Home Assistant config topic: '+configTopic)
|
||||
debug(discoveryData)
|
||||
this.publishMqtt(configTopic, JSON.stringify(discoveryData))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimpleDimmer
|
||||
45
devices/simple-switch.js
Normal file
45
devices/simple-switch.js
Normal file
@@ -0,0 +1,45 @@
|
||||
const TuyaDevice = require('./tuya-device')
|
||||
const debug = require('debug')('tuya-mqtt:tuya')
|
||||
const utils = require('../lib/utils')
|
||||
|
||||
class SimpleSwitch extends TuyaDevice {
|
||||
async init() {
|
||||
// Set device specific variables
|
||||
this.config.dpsPower = this.config.dpsPower ? this.config.dpsPower : 1
|
||||
|
||||
this.deviceData.mdl = 'Switch/Socket'
|
||||
|
||||
// Map generic DPS topics to device specific topic names
|
||||
this.deviceTopics = {
|
||||
state: {
|
||||
key: this.config.dpsPower,
|
||||
type: 'bool'
|
||||
}
|
||||
}
|
||||
|
||||
// Send home assistant discovery data and give it a second before sending state updates
|
||||
this.initDiscovery()
|
||||
await utils.sleep(1)
|
||||
|
||||
// Get initial states and start publishing topics
|
||||
this.getStates()
|
||||
}
|
||||
|
||||
initDiscovery() {
|
||||
const configTopic = 'homeassistant/switch/'+this.config.id+'/config'
|
||||
|
||||
const discoveryData = {
|
||||
name: (this.config.name) ? this.config.name : this.config.id,
|
||||
state_topic: this.baseTopic+'state',
|
||||
command_topic: this.baseTopic+'command',
|
||||
unique_id: this.config.id,
|
||||
device: this.deviceData
|
||||
}
|
||||
|
||||
debug('Home Assistant config topic: '+configTopic)
|
||||
debug(discoveryData)
|
||||
this.publishMqtt(configTopic, JSON.stringify(discoveryData))
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = SimpleSwitch
|
||||
420
devices/tuya-device.js
Normal file
420
devices/tuya-device.js
Normal file
@@ -0,0 +1,420 @@
|
||||
const TuyAPI = require('tuyapi')
|
||||
const utils = require('../lib/utils')
|
||||
const debug = require('debug')('tuya-mqtt:tuya')
|
||||
const debugMqtt = require('debug')('tuya-mqtt:mqtt')
|
||||
const debugError = require('debug')('tuya-mqtt:error')
|
||||
|
||||
class TuyaDevice {
|
||||
constructor(deviceInfo) {
|
||||
this.config = deviceInfo.configDevice
|
||||
this.mqttClient = deviceInfo.mqttClient
|
||||
this.topic = deviceInfo.topic
|
||||
|
||||
// Build TuyAPI device options from device config info
|
||||
this.options = {
|
||||
id: this.config.id,
|
||||
key: this.config.key
|
||||
}
|
||||
if (this.config.name) { this.options.name = this.config.name.toLowerCase().replace(/ /g,'_') }
|
||||
if (this.config.ip) {
|
||||
this.options.ip = this.config.ip
|
||||
if (this.config.version) {
|
||||
this.options.version = this.config.version
|
||||
} else {
|
||||
this.options.version = '3.1'
|
||||
}
|
||||
}
|
||||
|
||||
// Set default device data for Home Assistant device registry
|
||||
// Values may be overridden by individual devices
|
||||
this.deviceData = {
|
||||
ids: [ this.config.id ],
|
||||
name: (this.config.name) ? this.config.name : this.config.id,
|
||||
mf: 'Tuya'
|
||||
}
|
||||
|
||||
this.dps = {} // This will hold dps state data for device
|
||||
this.prevDps = {} // This will hold previous dps value for device to avoid republish of non-changed states
|
||||
|
||||
// Build the MQTT topic for this device (friendly name or device id)
|
||||
if (this.options.name) {
|
||||
this.baseTopic = this.topic + this.options.name + '/'
|
||||
} else {
|
||||
this.baseTopic = this.topic + this.options.id + '/'
|
||||
}
|
||||
|
||||
// Create the new Tuya Device
|
||||
this.device = new TuyAPI(JSON.parse(JSON.stringify(this.options)))
|
||||
|
||||
// Listen for device data and call update DPS function if valid
|
||||
this.device.on('data', (data) => {
|
||||
if (typeof data == 'string') {
|
||||
debug('Data from device not encrypted:', data.replace(/[^a-zA-Z0-9 ]/g, ''))
|
||||
} else {
|
||||
if (!(data.dps['1'] === null && data.dps['2'] === null && data.dps['3'] === null && data.dps['101'] === null && data.dps['102'] === null && data.dps['103'] === null)) {
|
||||
debug('Data from device '+this.options.id+' ->', data.dps)
|
||||
this.updateDpsData(data)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
// Find device on network
|
||||
debug('Search for device id '+this.options.id)
|
||||
this.device.find().then(() => {
|
||||
debug('Found device id '+this.options.id)
|
||||
// Attempt connection to device
|
||||
this.device.connect()
|
||||
})
|
||||
|
||||
// On connect perform device specific init
|
||||
this.device.on('connected', () => {
|
||||
debug('Connected to device ' + this.toString())
|
||||
this.init()
|
||||
})
|
||||
|
||||
// On disconnect perform device specific disconnect
|
||||
this.device.on('disconnected', () => {
|
||||
this.connected = false
|
||||
debug('Disconnected from device ' + this.toString())
|
||||
})
|
||||
|
||||
// On connect error call reconnect
|
||||
this.device.on('error', (err) => {
|
||||
if (err !== 'json obj data unvalid') {
|
||||
debugError(err)
|
||||
}
|
||||
if (err.message === 'Error from socket') {
|
||||
this.reconnect()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// Retry connection every 10 seconds if unable to connect
|
||||
async reconnect() {
|
||||
debug('Error connecting to device id '+this.options.id+'...retry in 10 seconds.')
|
||||
await utils.sleep(10)
|
||||
if (this.connected) { return }
|
||||
debug('Search for device id '+this.options.id)
|
||||
this.device.find().then(() => {
|
||||
debug('Found device id '+this.options.id)
|
||||
// Attempt connection to device
|
||||
this.device.connect()
|
||||
})
|
||||
}
|
||||
|
||||
// Publish MQTT
|
||||
publishMqtt(topic, message, isDebug) {
|
||||
if (isDebug) { debugMqtt(topic, message) }
|
||||
this.mqttClient.publish(topic, message, { qos: 1 });
|
||||
}
|
||||
|
||||
// Publish device specific state topics
|
||||
publishTopics() {
|
||||
// Don't publish if device is not connected
|
||||
if (!this.connected) return
|
||||
|
||||
// Loop through and publish all device specific topics
|
||||
for (let topic in this.deviceTopics) {
|
||||
const state = this.getTopicState(topic)
|
||||
this.publishMqtt(this.baseTopic + topic, state, true)
|
||||
}
|
||||
|
||||
// Publish Generic Dps Topics
|
||||
this.publishDpsTopics()
|
||||
}
|
||||
|
||||
// Process MQTT commands for all command topics at device level
|
||||
processDeviceCommand(message, commandTopic) {
|
||||
// Determine state topic from command topic to find proper template
|
||||
const stateTopic = commandTopic.replace('command', 'state')
|
||||
const deviceTopic = this.deviceTopics.hasOwnProperty(stateTopic) ? this.deviceTopics[stateTopic] : ''
|
||||
|
||||
if (deviceTopic) {
|
||||
debug('Device '+this.options.id+' recieved command topic: '+commandTopic+', message: '+message)
|
||||
const command = this.getCommandFromMessage(message)
|
||||
let setResult = this.setState(command, deviceTopic)
|
||||
if (!setResult) {
|
||||
debug('Command topic '+this.baseTopic+commandTopic+' received invalid value: '+command)
|
||||
}
|
||||
} else {
|
||||
debug('Invalid command topic '+this.baseTopic+commandTopic+' for device: '+this.config.name)
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// Get and update state of all dps properties for device
|
||||
async getStates() {
|
||||
// Suppress topic updates while syncing state
|
||||
this.connected = false
|
||||
for (let topic in this.deviceTopics) {
|
||||
const key = this.deviceTopics[topic].key
|
||||
const result = await this.device.get({"dps": key})
|
||||
}
|
||||
this.connected = true
|
||||
// Force topic update now that all states are fully in sync
|
||||
this.publishTopics()
|
||||
}
|
||||
|
||||
// Update dps properties with device data updates
|
||||
updateDpsData(data) {
|
||||
try {
|
||||
if (typeof data.dps != 'undefined') {
|
||||
// Update device dps values
|
||||
for (let key in data.dps) {
|
||||
this.dps[key] = data.dps[key]
|
||||
}
|
||||
if (this.connected) {
|
||||
this.publishTopics()
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
debugError(e);
|
||||
}
|
||||
}
|
||||
|
||||
// Process MQTT commands for all command topics at device level
|
||||
async processCommand(message, commandTopic) {
|
||||
const command = this.getCommandFromMessage(message)
|
||||
if (commandTopic === 'command' && command === 'get-states' ) {
|
||||
// Handle "get-states" command to update device state
|
||||
debug('Received command: ', command)
|
||||
await this.getStates()
|
||||
} else {
|
||||
// Call device specific command topic handler
|
||||
this.processDeviceCommand(message, commandTopic)
|
||||
}
|
||||
}
|
||||
|
||||
// Publish all dps-values to topic
|
||||
publishDpsTopics() {
|
||||
try {
|
||||
const dpsTopic = this.baseTopic + 'dps'
|
||||
|
||||
// Publish DPS JSON data if not empty
|
||||
if (Object.keys(this.dps).length) {
|
||||
const data = JSON.stringify(this.dps)
|
||||
const dpsStateTopic = dpsTopic + '/state'
|
||||
debugMqtt('MQTT DPS JSON: ' + dpsStateTopic + ' -> ', data)
|
||||
this.publishMqtt(dpsStateTopic, data, false)
|
||||
}
|
||||
|
||||
// Publish dps/<#>/state value for each device DPS
|
||||
for (let key in this.dps) {
|
||||
const dpsKeyTopic = dpsTopic + '/' + key + '/state'
|
||||
const data = this.dps.hasOwnProperty(key) ? this.dps[key].toString() : 'None'
|
||||
debugMqtt('MQTT DPS'+key+': '+dpsKeyTopic+' -> ', data)
|
||||
this.publishMqtt(dpsKeyTopic, data, false)
|
||||
}
|
||||
} catch (e) {
|
||||
debugError(e);
|
||||
}
|
||||
}
|
||||
|
||||
getTopicState(topic) {
|
||||
const deviceTopic = this.deviceTopics[topic]
|
||||
const key = deviceTopic.key
|
||||
let state = null
|
||||
switch (deviceTopic.type) {
|
||||
case 'bool':
|
||||
state = this.dps[key] ? 'ON' : 'OFF'
|
||||
break;
|
||||
case 'int':
|
||||
state = this.dps[key] ? this.dps[key].toString() : 'None'
|
||||
break;
|
||||
case 'hsb':
|
||||
if (this.dps[key]) {
|
||||
state = this.getColorState(this.dps[key], topic)
|
||||
}
|
||||
break;
|
||||
case 'str':
|
||||
state = this.dps[key] ? this.dps[key] : ''
|
||||
}
|
||||
return state
|
||||
}
|
||||
|
||||
// Set state based on command topic
|
||||
setState(command, deviceTopic) {
|
||||
const tuyaCommand = new Object()
|
||||
tuyaCommand.dps = deviceTopic.key
|
||||
switch (deviceTopic.type) {
|
||||
case 'bool':
|
||||
if (command === 'toggle') {
|
||||
tuyaCommand.set = !this.dps[tuyaCommand.dps]
|
||||
} else {
|
||||
if (typeof command.set === 'boolean') {
|
||||
tuyaCommand.set = command.set
|
||||
} else {
|
||||
tuyaCommand.set = '!!!INVALID!!!'
|
||||
}
|
||||
}
|
||||
break;
|
||||
case 'int':
|
||||
if (isNaN(command)) {
|
||||
tuyaCommand.set = '!!!INVALID!!!'
|
||||
} else if (deviceTopic.hasOwnProperty('min') && deviceTopic.hasOwnProperty('max')) {
|
||||
tuyaCommand.set = (command >= deviceTopic.min && command <= deviceTopic.max ) ? parseInt(command) : '!!!INVALID!!!'
|
||||
} else {
|
||||
tuyaCommand.set = parseInt(command)
|
||||
}
|
||||
break;
|
||||
case 'hsb':
|
||||
tuyaCommand.set = this.getColorCommand(command, deviceTopic)
|
||||
this.setLightMode(deviceTopic)
|
||||
break;
|
||||
}
|
||||
if (tuyaCommand.set === '!!!INVALID!!!') {
|
||||
return false
|
||||
} else {
|
||||
if (this.config.dpsWhiteValue === deviceTopic.key) {
|
||||
this.setLightMode(deviceTopic)
|
||||
}
|
||||
this.set(tuyaCommand)
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
// Converts message to TuyAPI JSON commands
|
||||
getCommandFromMessage(_message) {
|
||||
let command = _message
|
||||
|
||||
if (command != '1' && command != '0' && utils.isJsonString(command)) {
|
||||
debugMqtt('MQTT message is JSON');
|
||||
command = JSON.parse(command);
|
||||
} else {
|
||||
switch(command.toLowerCase()) {
|
||||
case 'on':
|
||||
case 'off':
|
||||
case '0':
|
||||
case '1':
|
||||
case 'true':
|
||||
case 'false':
|
||||
// convert simple commands (on, off, 1, 0) to TuyAPI-Commands
|
||||
const convertString = command.toLowerCase() === 'on' || command === '1' || command === 'true' || command === 1 ? true : false;
|
||||
command = {
|
||||
set: convertString
|
||||
}
|
||||
break;
|
||||
default:
|
||||
command = command.toLowerCase();
|
||||
}
|
||||
}
|
||||
return command;
|
||||
}
|
||||
|
||||
// Process Tuya JSON commands via DPS command topic
|
||||
processDpsCommand(message) {
|
||||
if (utils.isJsonString(message)) {
|
||||
const tuyaCommand = this.getCommandFromMessage(message)
|
||||
debugMqtt('Received command: '+tuyaCommand)
|
||||
this.set(tuyaCommand)
|
||||
} else {
|
||||
debugError('DPS command topic requires Tuya style JSON value')
|
||||
}
|
||||
}
|
||||
|
||||
// Process text base Tuya command via DPS key command topics
|
||||
processDpsKeyCommand(message, dpsKey) {
|
||||
if (utils.isJsonString(message)) {
|
||||
debugError('Individual DPS command topics do not accept JSON values')
|
||||
} else {
|
||||
const dpsMessage = this.parseDpsMessage(message)
|
||||
debugMqtt('Received command for DPS'+dpsKey+': ', message)
|
||||
const tuyaCommand = {
|
||||
dps: dpsKey,
|
||||
set: dpsMessage
|
||||
}
|
||||
this.set(tuyaCommand)
|
||||
}
|
||||
}
|
||||
|
||||
// Parse string message into boolean and number types
|
||||
parseDpsMessage(message) {
|
||||
if (typeof message === 'boolean' ) {
|
||||
return message;
|
||||
} else if (message === 'true' || message === 'false') {
|
||||
return (message === 'true') ? true : false
|
||||
} else if (!isNaN(message)) {
|
||||
return Number(message)
|
||||
} else {
|
||||
return message
|
||||
}
|
||||
}
|
||||
|
||||
// Simple function to help debug output
|
||||
toString() {
|
||||
return this.config.name+' (' +(this.options.ip ? this.options.ip+', ' : '')+this.options.id+', '+this.options.key+')'
|
||||
}
|
||||
|
||||
set(command) {
|
||||
debug('Set device '+this.options.id+' -> '+command)
|
||||
return new Promise((resolve, reject) => {
|
||||
this.device.set(command).then((result) => {
|
||||
debug(result)
|
||||
resolve(result)
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
// Takes the current Tuya color and splits it into component parts
|
||||
// Returns decimal format comma delimeted string of components for selected topic
|
||||
getColorState(value, topic) {
|
||||
const [, h, s, b] = (value || '000003e803e8').match(/^([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})$/i) || [0, '0', '3e8', '3e8'];
|
||||
const decimalColor = {
|
||||
h: parseInt(h, 16),
|
||||
s: Math.round(parseInt(s, 16) / 10),
|
||||
b: parseInt(b, 16)
|
||||
}
|
||||
const color = new Array()
|
||||
const components = this.deviceTopics[topic].components.split(',')
|
||||
for (let i in components) {
|
||||
if (decimalColor.hasOwnProperty([components[i]])) {
|
||||
color.push(decimalColor[components[i]])
|
||||
}
|
||||
}
|
||||
return (color.join(','))
|
||||
}
|
||||
|
||||
// Takes provided decimal HSB components from MQTT topic, combine with existing
|
||||
// settings for unchanged values since brightness is sometimes sent separately
|
||||
// Convert to Tuya hex format and return value
|
||||
getColorCommand(value, topic) {
|
||||
const [, h, s, b] = (this.dps[topic.key] || '000003e803e8').match(/^([0-9a-f]{4})([0-9a-f]{4})([0-9a-f]{4})$/i) || [0, '0', '3e8', '3e8'];
|
||||
const decimalColor = {
|
||||
h: parseInt(h, 16),
|
||||
s: Math.round(parseInt(s, 16) / 10),
|
||||
b: parseInt(b, 16)
|
||||
}
|
||||
const components = topic.components.split(',')
|
||||
const values = value.split(',')
|
||||
for (let i in components) {
|
||||
decimalColor[components[i]] = Math.round(values[i])
|
||||
}
|
||||
const hexColor = decimalColor.h.toString(16).padStart(4, '0') + (10 * decimalColor.s).toString(16).padStart(4, '0') + (decimalColor.b).toString(16).padStart(4, '0')
|
||||
return hexColor
|
||||
}
|
||||
|
||||
// Set light mode based on received command
|
||||
async setLightMode(topic) {
|
||||
const currentMode = this.dps[this.config.dpsMode]
|
||||
let targetMode
|
||||
|
||||
if (this.config.dpsWhiteValue === topic.key) {
|
||||
// If setting white level, switch to white mode
|
||||
targetMode = 'white'
|
||||
} else if (this.config.dpsColor === topic.key) {
|
||||
// If setting an HSB value, switch to colour mode
|
||||
targetMode = 'colour'
|
||||
}
|
||||
|
||||
// Set the correct light mode
|
||||
if (targetMode && targetMode !== currentMode) {
|
||||
const tuyaCommand = {
|
||||
dps: this.config.dpsMode,
|
||||
set: targetMode
|
||||
}
|
||||
await this.set(tuyaCommand)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = TuyaDevice
|
||||
Reference in New Issue
Block a user