'use strict';
function AlexaSH2(adapter) {
let Actions = require(__dirname + '/../admin/actions');
let smartDevices = [];
let enums = [];
let valuesON = {};
let lang = 'de';
let translate = false;
let translateRooms;
let translateFunctions;
let translateDevices;
const words = {
'No name': {'en': 'No name', 'de': 'Kein Name', 'ru': 'Нет имени'},
'Group': {'en': 'Group', 'de': 'Gruppe', 'ru': 'Группа'}
};
this.setLanguage = function (_lang, _translate) {
lang = _lang;
translate = _translate;
};
/* function validateName(name) {
if (!name) return false;
for (let n = 0; n < name.length; n++) {
if (name[n] === ';' || name[n] === '.' || name[n] === '-' || name[n] === ':') return false;
}
return true;
} */
function getSmartName(states, id) {
if (!id) {
if (!adapter.config.noCommon) {
return states.common.smartName;
} else {
return (states &&
states.common &&
states.common.custom &&
states.common.custom[adapter.namespace]) ?
states.common.custom[adapter.namespace].smartName : undefined;
}
} else
if (!adapter.config.noCommon) {
return states[id].common.smartName;
} else {
return (states[id] &&
states[id].common &&
states[id].common.custom &&
states[id].common.custom[adapter.namespace]) ?
states[id].common.custom[adapter.namespace].smartName || null : null;
}
}
function padding(num) {
num = num.toString(16);
if (num.length < 2) num = '0' + num;
return num;
}
function writeResponse(applianceId, operation, value) {
for (let d = 0; d < smartDevices.length; d++) {
if (smartDevices[d].applianceId === applianceId) {
let text;
let obj = smartDevices[d];
switch (operation) {
case 'ONOFF':
if (value) {
if (lang === 'de') {
text = obj.friendlyName + ' ist eingeschaltet!';
} else if (lang === 'ru') {
text = obj.friendlyName + ' в состоянии включено';
} else {
text = obj.friendlyName + ' turned on';
}
} else {
if (lang === 'de') {
text = obj.friendlyName + ' ist ausgeschaltet!';
} else if (lang === 'ru') {
text = obj.friendlyName + ' в состоянии выключено';
} else {
text = obj.friendlyName + ' turned off';
}
}
break;
case '%':
if (lang === 'de') {
text = obj.friendlyName + ' wird auf ' + value + ' Prozent gesetzt';
} else if (lang === 'ru') {
text = 'Состояние ' + obj.friendlyName + ' установлено на ' + value;
} else {
text = obj.friendlyName + ' set to ' + value + ' percent';
}
break;
case '<>':
if (value >= 0) {
if (lang === 'de') {
text = obj.friendlyName + ' ist erhöht um ' + value + ' Prozent';
} else if (lang === 'ru') {
text = 'Состояние ' + obj.friendlyName + ' увеличено на ' + value;
} else {
text = obj.friendlyName + ' increased on ' + value + ' percent';
}
value = '+' + value;
} else {
if (lang === 'de') {
text = obj.friendlyName + ' ist verkleinert um ' + value + ' Prozent';
} else if (lang === 'ru') {
text = 'Состояние ' + obj.friendlyName + ' уменьшено на ' + value;
} else {
text = obj.friendlyName + ' decreased on ' + value + ' percent';
}
value = '-' + value;
}
break;
}
if (text) {
adapter.setState('smart.lastResponse', text, true);
if (adapter.config.responseOID) {
adapter.setForeignState(adapter.config.responseOID, text, false);
}
adapter.setState('smart.lastFunction', obj.additionalApplianceDetails.func, true);
adapter.setState('smart.lastRoom', obj.additionalApplianceDetails.room, true);
adapter.setState('smart.lastCommand', value, true);
}
return;
}
}
adapter.log.warn('Unknown applianceId: ' + applianceId);
}
/*function findRole(states, id, role) {
let parts = id.split('.');
parts.pop();
let channel = parts.join('.') + '.';
for (const i in states) {
if (states.hasOwnProperty(i) &&
i.substring(0, channel.length) === channel &&
i !== id &&
states[i].common &&
states[i].common.role === role) {
return i;
}
}
return null;
}*/
function processState(states, id, room, func, alexaIds, groups, names, result) {
// Make sure to also add other needed Endpoints:
/*adapter.log.debug('Process: ' + id);
if(states[id].common.role == 'level.color.hue') {
let sat = findRole(states,id,'level.color.saturation');
let bri = findRole(states,id,'level.dimmer');
let on = findRole(states,id,'switch');
adapter.log.debug('Adding needed: ' + sat + ' ' + bri + ' ' + on);
if(sat) processState(states, sat, room, func, alexaIds, groups, names, result);
if(bri) processState(states, bri, room, func, alexaIds, groups, names, result);
if(on) processState(states, on, room, func, alexaIds, groups, names, result);
}*/
try {
let friendlyName = getSmartName(states, id);
let nameModified = false;
let byON;
let smartType;
if (!id) {
return;
}
if (states[id] && states[id].native) {
// try to convert old notation to new one
if (!adapter.config.noCommon) {
if (states[id].native.byON) {
byON = states[id].native.byON;
delete states[id].native.byON;
let smartName = states[id].common.smartName;
if (!smartName || typeof smartName !== 'object') {
smartName = {
byON: byON,
en: smartName
};
smartName[lang] = smartName.en;
} else {
smartName.byON = byON;
}
states[id].common.smartName = smartName || {};
friendlyName = states[id].common.smartName;
} else if (typeof states[id].common.smartName === 'string') {
let nnn = states[id].common.smartName;
states[id].common.smartName = {};
states[id].common.smartName[lang] = nnn;
friendlyName = states[id].common.smartName;
}
}
} else {
adapter.log.debug('Invalid state "' + id + '". Not exist or no native part.');
return null;
}
byON = (friendlyName && typeof friendlyName === 'object') ? friendlyName.byON : '';
smartType = (friendlyName && typeof friendlyName === 'object') ? friendlyName.smartType : null;
if (typeof friendlyName === 'object' && friendlyName) {
friendlyName = friendlyName[lang] || friendlyName.en;
}
if (friendlyName === 'ignore' || friendlyName === false) {
return null;
}
if (!friendlyName && !room && !func) {
return null;
}
let friendlyNames = [];
if (!friendlyName) {
if (room) {
// translate room
if (translate) {
translateRooms = translateRooms || require(__dirname + '/rooms.js');
translateFunctions = translateFunctions || require(__dirname + '/functions.js');
room = translateRooms(lang, room);
func = translateFunctions(lang, func);
}
if (adapter.config.functionFirst) {
if (lang === 'en') {
friendlyName = func + (adapter.config.concatWord ? ' ' + adapter.config.concatWord : '') + ' ' + room;
} else {
friendlyName = func + (adapter.config.concatWord ? ' ' + adapter.config.concatWord : '') + ' ' + room;
}
} else {
if (lang === 'en') {
friendlyName = room + (adapter.config.concatWord ? ' ' + adapter.config.concatWord : '') + ' ' + func;
} else {
friendlyName = room + (adapter.config.concatWord ? ' ' + adapter.config.concatWord : '') + ' ' + func;
}
}
} else {
friendlyName = states[id].common.name;
if (adapter.config.replaces) {
for (let r = 0; r < adapter.config.replaces.length; r++) {
friendlyName = friendlyName.replace(adapter.config.replaces[r], '');
}
}
}
friendlyNames[0] = friendlyName;
nameModified = false;
} else if (translate) {
translateDevices = translateDevices || require(__dirname + '/devices.js');
friendlyName = translateDevices(lang, friendlyName);
nameModified = true;
friendlyNames = friendlyName.split(',');
} else {
friendlyNames = friendlyName.split(',');
nameModified = true;
}
for (let i = friendlyNames.length - 1; i >= 0; i--) {
friendlyNames[i] = (friendlyNames[i] || '').trim();
if (!friendlyNames[i]) {
friendlyNames.splice(i, 1);
} else {
// friendlyName may not be longer than 128
friendlyNames[i] = friendlyNames[i].substring(0, 128).replace(/[^.a-zA-Z0-9äÄüÜöÖß]+/g, ' ');
}
}
if (!friendlyNames[0]) {
adapter.log.warn('State ' + id + ' is invalid.');
return;
}
let friendlyDescription = (states[id].common.name || id);
let res = Actions.getActions(states[id]);
if (!res) {
adapter.log.debug('Name "' + (states[id].common.name || id) + '" cannot be written and will be ignored');
return;
}
let type = res.type;
let actions = res.actions;
friendlyDescription = friendlyDescription.substring(0, 128).replace(/[^a-zA-Z0-9äÄüÜöÖß]+/g, ' ');
// any letter or number and _ - = # ; : ? @ &
let applianceId = id.substring(0, 256).replace(/[^a-zA-Z0-9_=#;:?@&-]+/g, '_');
let pos;
if (alexaIds && (pos = alexaIds.indexOf(id)) !== -1) {
alexaIds.splice(pos, 1);
}
type = type ? (byON || '100') : false;
let name = states[id].common.name ? states[id].common.name.substring(0, 128) : '';
for (let n = 0; n < friendlyNames.length; n++) {
let obj = {
applianceId: friendlyNames[n].replace(/[^a-zA-Z0-9_=#;:?@&-]+/g, '_'),
applianceTypes: [],
manufacturerName: 'ioBroker',
modelName: (states[id].common.name || words['No name'][lang]).substring(0, 128),
version: '1',
friendlyName: friendlyNames[n],
friendlyDescription: friendlyDescription,
isReachable: true,
actions: JSON.parse(JSON.stringify(actions)),
additionalApplianceDetails: {
id: id.substring(0, 1024),
role: states[id].common.role,
name: name,
friendlyNames: friendlyNames.join(', '),
smartType: smartType,
byON: type,
nameModified: nameModified,
room: room,
func: func
}
};
if (names[friendlyNames[n]]) {
// Ignore it, because yet in the list
if (names[friendlyNames[n]].additionalApplianceDetails.id === id) return;
// create virtual group
if (groups[friendlyNames[n]]) {
/* // let ids = JSON.parse(groups[friendlyNames[n]].additionalApplianceDetails.ids);
let _names = JSON.parse(groups[friendlyNames[n]].additionalApplianceDetails.names || '[]');
let types = JSON.parse(groups[friendlyNames[n]].additionalApplianceDetails.byONs || '[]');
// ids.push(id);
_names.push(name);
types.push(type);
// merge actions
for (let a = 0; a < actions.length; a++) {
if (groups[friendlyNames[n]].actions.indexOf(actions[a]) === -1) {
groups[friendlyNames[n]].actions.push(actions[a]);
}
}
// groups[friendlyNames[n]].additionalApplianceDetails.ids = JSON.stringify(ids);
groups[friendlyNames[n]].additionalApplianceDetails.names = JSON.stringify(_names);
groups[friendlyNames[n]].additionalApplianceDetails.byONs = JSON.stringify(types);*/
for (let a = 0; a < actions.length; a++) {
if (groups[friendlyNames[n]].actions.indexOf(actions[a]) === -1) {
groups[friendlyNames[n]].actions.push(actions[a]);
}
}
if (smartType && groups[friendlyNames[n]].applianceTypes.indexOf(smartType) === -1) {
groups[friendlyNames[n]].applianceTypes.push(smartType);
}
groups[friendlyNames[n]].additionalApplianceDetails.byONs[id] = type;
groups[friendlyNames[n]].additionalApplianceDetails.names[id] = name;
groups[friendlyNames[n]].additionalApplianceDetails.smartTypes[id] = smartType;
let addids = [id];
for (let i = 0; i < addids.length; i++) {
let newid = addids[i];
let _parts = newid.split('.');
_parts.pop();
let channel = _parts.join('.');
if (groups[friendlyNames[n]].additionalApplianceDetails.channels.hasOwnProperty(channel)) {
groups[friendlyNames[n]].additionalApplianceDetails.channels[channel].push({id: newid, role: states[addids[i]].common.role});
} else {
groups[friendlyNames[n]].additionalApplianceDetails.channels[channel] = [{id: newid, role: states[addids[i]].common.role}];
}
}
} else {
groups[friendlyNames[n]] = {
applianceId: friendlyNames[n].replace(/[^a-zA-Z0-9_=#;:?@&-]+/g, '_'),
applianceTypes: JSON.parse(JSON.stringify(names[friendlyNames[n]].applianceTypes)),
manufacturerName: 'ioBroker group',
modelName: (states[id].common.name || words['No name'][lang]).substring(0, 128),
version: '1',
friendlyName: friendlyNames[n],
friendlyDescription: words['Group'][lang] + ' ' + friendlyNames[n],
isReachable: true,
actions: JSON.parse(JSON.stringify(actions)),
additionalApplianceDetails: {
group: true,
channels: {},
smartTypes: {},
names: {},
byONs: {},
room: room,
func: func
}
};
// merge actions for the first group, too!
for (let a = 0; a < names[friendlyNames[n]].actions.length; a++) {
if (groups[friendlyNames[n]].actions.indexOf(names[friendlyNames[n]].actions[a]) === -1) {
groups[friendlyNames[n]].actions.push(names[friendlyNames[n]].actions[a]);
}
}
let oldobjDetails = names[friendlyNames[n]].additionalApplianceDetails;
if (smartType && groups[friendlyNames[n]].applianceTypes.indexOf(smartType) === -1) {
groups[friendlyNames[n]].applianceTypes.push(smartType);
}
groups[friendlyNames[n]].additionalApplianceDetails.byONs[oldobjDetails.id] = oldobjDetails.byON;
groups[friendlyNames[n]].additionalApplianceDetails.names[oldobjDetails.id] = oldobjDetails.name;
groups[friendlyNames[n]].additionalApplianceDetails.smartTypes[oldobjDetails.id] = oldobjDetails.smartType;
groups[friendlyNames[n]].additionalApplianceDetails.byONs[id] = type;
groups[friendlyNames[n]].additionalApplianceDetails.names[id] = name;
groups[friendlyNames[n]].additionalApplianceDetails.smartTypes[id] = smartType;
let addids = [names[friendlyNames[n]].additionalApplianceDetails.id, id];
for (let i = 0; i < addids.length; i++) {
let newid = addids[i];
let _parts = newid.split('.');
_parts.pop();
let channel = _parts.join('.');
if (groups[friendlyNames[n]].additionalApplianceDetails.channels.hasOwnProperty(channel)) {
groups[friendlyNames[n]].additionalApplianceDetails.channels[channel].push({id: newid, role: states[addids[i]].common.role});
} else {
groups[friendlyNames[n]].additionalApplianceDetails.channels[channel] = [{id: newid, role: states[addids[i]].common.role}];
}
}
result.push(groups[friendlyNames[n]]);
names[friendlyNames[n]].disabled = true;
}
obj = null;
} else {
names[friendlyNames[n]] = obj;
if (smartType) names[friendlyNames[n]].applianceTypes.push(smartType);
}
if (obj) result.push(obj);
}
} catch (e) {
adapter.log.error('Cannot process "' + id + '": ' + e);
}
}
function controlOnOff(toggle, id, value, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
let smartName = getSmartName(obj);
let byON = (smartName && typeof smartName === 'object') ? smartName.byON : null;
if (toggle && toggle !== id){
adapter.log.debug('Won\'t control ' + id + ' as we have a switch available.');
if (callback) callback(err);
return;
}
if (obj.common.type === 'number') {
// if ON
if (value) {
if (byON === 'stored' && valuesON[id]) {
adapter.log.debug('Use stored ON value for "' + id + '": ' + valuesON[id]);
value = valuesON[id];
} else {
if (typeof obj.common.max !== 'undefined') {
value = byON ? parseFloat(byON) || obj.common.max : obj.common.max;
} else {
value = byON ? parseFloat(byON) || 100 : 100;
}
}
} else {
// if OFF
if (byON === 'stored') {
// remember last state
adapter.getForeignState(id, function (err, state) {
if (err) adapter.log.error('Cannot get state: ' + err);
if (state) valuesON[id] = state.val;
if (typeof obj.common.min !== 'undefined') {
value = obj.common.min;
} else {
value = 0;
}
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
if (callback) callback(err);
});
});
return;
} else {
// blinds
if (typeof obj.common.min !== 'undefined') {
value = obj.common.min;
} else {
value = 0;
}
}
}
} else if (obj.common.role === 'level.color.rgb') {
if (value) {
value = '#FFFFFF';
} else {
value = '#000000';
}
}
adapter.log.debug('Set "' + id + '" to ' + value);
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
if (callback) callback(err);
});
});
}
function controlPercent(dimmer, id, value, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
if (dimmer && dimmer !== id) {
adapter.log.debug('Won\'t control ' + id + ' as we have a dimmer available for this channel.');
if (callback) callback();
return;
}
adapter.log.debug('Controlling ' + id + ' .');
let max = 100;
let min = 0;
if (typeof obj.common.max !== 'undefined') max = parseFloat(obj.common.max);
if (typeof obj.common.min !== 'undefined') min = parseFloat(obj.common.min);
if (value < 0) value = 0;
if (value > 100) value = 100;
value = (value / 100) * (max - min) + min;
if (obj.common.type === 'boolean') {
value = (value >= adapter.config.deviceOffLevel);
} else if (adapter.config.deviceOffLevel && value >= adapter.config.deviceOffLevel && (!obj.common.role || obj.common.role.indexOf('blind') === -1)) {
valuesON[id] = value;
adapter.log.debug('Remember ON value for "' + id + '": ' + value);
}
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
if (callback) callback();
});
});
}
function controlDelta(dimmer, id, delta, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
if (obj.common.type === 'boolean') {
adapter.log.debug('Object ' + id + ' does not support Deltas.');
if (callback) callback();
return;
}
if (dimmer && dimmer !== id) {
adapter.log.debug('Won\'t control ' + id + ' as we have a dimmer available for this channel.');
if (callback) callback();
return;
}
adapter.log.debug('Controlling ' + id + ' .');
adapter.getForeignState(id, function (err, state) {
let value = state ? (state.val || 0) : 0;
let max = 100;
let min = 0;
if (typeof obj.common.max !== 'undefined') max = parseFloat(obj.common.max);
if (typeof obj.common.min !== 'undefined') min = parseFloat(obj.common.min);
// Absolute value => percent => add delta
value = (value - min) / (max - min) * 100 + delta;
if (value > 100) value = 100;
if (value < 0) value = 0;
// percent => absolute value
value = (value / 100) * (max - min) + min;
if (adapter.config.deviceOffLevel && value >= adapter.config.deviceOffLevel) {
adapter.log.debug('Remember ON value for "' + id + '": ' + value);
valuesON[id] = value;
}
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot set device: ' + err);
if (callback) callback();
});
});
});
}
function controlTemperature(id, value, writeStates, callback) {
//{
// "header" : {
// "namespace" : "Alexa.ConnectedHome.Control",
// "name" : "SetTargetTemperatureConfirmation",
// "payloadVersion" : "2",
// "messageId" : "cc36e80c-6357-41e0-9dd4-b76cb3a394e3"
// },
// "payload" : {
// "targetTemperature" : {
// "value" : 25.0
// },
// "temperatureMode" : {
// "value" : "AUTO"
// },
// "previousState" : {
// "targetTemperature" : {
// "value" : 21.0
// },
// "mode" : {
// "value" : "AUTO"
// }
// }
// }
//}
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
value = parseFloat(value);
let max;
let min;
if (typeof obj.common.max !== 'undefined') max = parseFloat(obj.common.max);
if (typeof obj.common.min !== 'undefined') min = parseFloat(obj.common.min);
if (min !== undefined && value < min) value = min;
if (max !== undefined && value > max) value = max;
if (obj.common.type === 'boolean') value = !!value;
adapter.getForeignState(id, function (err, state) {
if (err) adapter.log.error('Cannot read device: ' + err);
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
let response = {
payload: {
targetTemperature: {
value: value
},
/*temperatureMode: {
value: 'AUTO'
},*/
previousState: {
targetTemperature: {
value: state ? parseFloat(state.val) || 0 : 0
},
mode: {
value: 'AUTO'
}
}
}
};
if (callback) callback(null, response);
});
});
});
}
function getTemperature(id, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
adapter.getForeignState(id, function (err, state) {
if (err) adapter.log.error('Cannot read device: ' + err);
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
if (callback) callback(null, state ? state.val : null, state ? state.ts : null);
});
});
}
function getTargetTemperature(id, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
adapter.getForeignState(id, function (err, state) {
if (err) adapter.log.error('Cannot read device: ' + err);
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
if (callback) callback(null, state ? state.val : null, state ? state.ts : null);
});
});
}
function controlTemperatureDelta(id, delta, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback(err || ('Invalid object: ' + id));
return;
}
adapter.getForeignState(id, function (err, state) {
if (err) adapter.log.error('Cannot read device: ' + err);
let value = state ? state.val || 0 : 0;
let max;
let min;
if (typeof obj.common.max !== 'undefined') max = parseFloat(obj.common.max);
if (typeof obj.common.min !== 'undefined') min = parseFloat(obj.common.min);
// Absolute value => percent => add delta
value = value + delta;
if (max !== undefined && value > max) value = max;
if (min !== undefined && value < min) value = min;
if (obj.common.type === 'boolean') value = !!value;
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
let response = {
payload: {
targetTemperature: {
value: value
},
/*temperatureMode: {
value: 'AUTO'
},*/
previousState: {
targetTemperature: {
value: state ? parseFloat(state.val) || 0 : 0
},
mode: {
value: 'AUTO'
}
}
}
};
if (callback) callback(err, response);
});
});
});
}
function controlLock(id, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
adapter.log.debug('Lock "' + id + '"');
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
if (obj.native.LOCK_VALUE === undefined) {
adapter.log.warn('Cannot choose value for lock: Please define in "' + id + '" the "native.LOCK_VALUE" with locking value');
if (callback) callback('Cannot choose value for lock');
} else {
adapter.setForeignState(id, obj.native.LOCK_VALUE, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
if (callback) callback();
});
}
});
}
function getLock(id, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
adapter.log.debug('Get lock state "' + id + '"');
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
if (obj.native.LOCK_VALUE === undefined) {
adapter.log.warn('Cannot choose value for lock: Please define in "' + id + '" the "native.LOCK_VALUE" with locking value');
if (callback) callback('Cannot choose value for lock');
} else {
adapter.getForeignState(id, function (err, state) {
if (err || !state) adapter.log.error('Cannot switch device: ' + err);
if (callback) {
if (obj.native.LOCK_VALUE === 'true' || obj.native.LOCK_VALUE === '1' || obj.native.LOCK_VALUE === 1 || obj.native.LOCK_VALUE === 'locked') obj.native.LOCK_VALUE = true;
if (obj.native.LOCK_VALUE === 'false' || obj.native.LOCK_VALUE === '0' || obj.native.LOCK_VALUE === 0 || obj.native.LOCK_VALUE === 'unlocked') obj.native.LOCK_VALUE = false;
if (state.val === 'true' || state.val === '1' || state.val === 1 || state.val === 'locked') state.val = true;
if (state.val === 'false' || state.val === '0' || state.val === 0 || state.val === 'unlocked') state.val = false;
callback(err, state.val === obj.native.LOCK_VALUE, state.lc || state.ts);
}
});
}
});
}
// expected hue range: [0, 1)
// expected saturation range: [0, 1]
// expected lightness range: [0, 1]
// Based on http://axonflux.com/handy-rgb-to-hsl-and-rgb-to-hsv-color-model-c
function hsvToRgb(h, s, v) {
let r;
let g;
let b;
let i = Math.floor(h * 6);
let f = h * 6 - i;
let p = v * (1 - s);
let q = v * (1 - f * s);
let t = v * (1 - (1 - f) * s);
switch(i % 6){
case 0:
r = v;
g = t;
b = p;
break;
case 1:
r = q;
g = v;
b = p;
break;
case 2:
r = p;
g = v;
b = t;
break;
case 3:
r = p;
g = q;
b = v;
break;
case 4:
r = t;
g = p;
b = v;
break;
case 5:
r = v;
g = p;
b = q;
break;
}
return '#' + padding(Math.round(r * 255)) + padding(Math.round(g * 255)) + padding(Math.round(b * 255));
}
function controlColorRgb(id, color, writeStates, callback) {
adapter.log.debug('ID: ' + id + ' Color: ' + JSON.stringify(color));
// "color": {
// "hue": 0.0,
// "saturation": 1.0000,
// "brightness": 1.0000
// }
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
let rgb = hsvToRgb(color.hue/360, color.saturation, color.brightness);
adapter.setForeignState(id, rgb, function (err) {
if (err) adapter.log.error('Cannot read device: ' + err);
if (writeStates) {
adapter.setState('smart.lastObjectID', id, true);
}
if (callback) callback(err);
});
});
}
function controlColorHue(ids, idh, idb, color, writeStates, callback) {
// "color": {
// "hue": 0.0,
// "saturation": 1.0000,
// "brightness": 1.0000
// }
adapter.getForeignObject(idh, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + idh + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
if (obj.common && obj.common.min !== undefined && obj.common.max !== undefined) {
color.hue = Math.round((obj.common.max - obj.common.min) * (color.hue / 360) + obj.common.min);
}
adapter.setForeignState(idh, color.hue, function (err) {
if (err) adapter.log.error('Cannot read device: ' + err);
if (writeStates) {
adapter.setState('smart.lastObjectID', idh, true);
}
if (!ids){
if (err) adapter.log.info('Unable to control saturation. No Saturation Endpoint in Smart Device.');
if (callback) callback();
return;
}
adapter.getForeignObject(ids, function (err, obj) {
if (obj.common && obj.common.min !== undefined && obj.common.max !== undefined) {
color.saturation = (obj.common.max - obj.common.min) * color.saturation + obj.common.min;
} else {
color.saturation *= 100;
}
adapter.setForeignState(ids, color.saturation, function (err) {
if (err) adapter.log.error('Cannot read device: ' + err);
if (!idb){
if (err) adapter.log.info('Unable to control brightness. No Dimmer Endpoint in Smart Device.');
if (callback) callback();
return;
}
adapter.getForeignObject(idb, function (err, obj) {
if (obj.common && obj.common.ignoreAlexaColor === 'true'){
if (callback) callback(err);
return;
}
if (obj.common && obj.common.min !== undefined && obj.common.max !== undefined) {
color.brightness = (obj.common.max - obj.common.min) * color.brightness + obj.common.min;
} else {
color.brightness *= 100;
}
adapter.setForeignState(idb, color.brightness, function (err) {
if (err) adapter.log.error('Cannot read device: ' + err);
if (callback) callback(err);
});
});
});
});
});
});
}
function controlColorTemperature(temperature, id, value, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback();
return;
}
if (!temperature || (temperature && temperature !== id)) {
adapter.log.debug('Won\'t control ' + id + ' as not a color temperature.');
if (callback) callback();
return;
}
const unit = obj.common.unit;
let friendlyUnit;
if (unit == null) {
// default to mired
friendlyUnit = 'mired';
} else if (unit.match('K')) {
friendlyUnit = 'kelvin';
} else if (unit === '%') {
friendlyUnit = 'percent';
}
// Alexa reports a value in Kelvin between 1000 and 10000
// By default we limit ourselves to the predefined values:
// warm, warm white 2200
// incandescent, soft white 2700
// white 4000
// daylight, daylight white 5500
// cool, cool white 7000
let minK = 2200;
let maxK = 7000;
if (friendlyUnit === 'kelvin') {
if (obj.common.min != null) minK = parseFloat(obj.common.min);
if (obj.common.max != null) maxK = parseFloat(obj.common.max);
value = Math.max(minK, Math.min(value, maxK));
} else { // % or mired
// for non-kelvin scales we have to re-calculate the value
// The internal representation is in mired
value = 1e6 / value;
let minM = Math.round(1e6 / maxK);
let maxM = Math.round(1e6 / minK);
if (friendlyUnit === 'mired') {
// respect the defined min/max
if (obj.common.min != null) minM = parseFloat(obj.common.min);
if (obj.common.max != null) maxM = parseFloat(obj.common.max);
} else if (friendlyUnit === 'percent') {
// scale the value from minM (0%) to maxM (100%)
value = (value - minM) / (maxM - minM) * 100;
// set the limits to 0..100
minM = 0;
maxM = 100;
}
value = Math.round(value);
value = Math.max(minM, Math.min(value, maxM));
}
adapter.log.debug('Controlling: ' + id + ' setting to ' + value);
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
if (callback) callback();
});
});
}
function controlColorTemperatureDelta(temperature, id, delta, writeStates, callback) {
adapter.getForeignObject(id, function (err, obj) {
if (!obj || obj.type !== 'state') {
if (err) adapter.log.error('Cannot control non state: ' + id + ' ' + (obj ? obj.type : 'no object'));
if (callback) callback('Not controlling');
return;
}
if (!temperature || (temperature && temperature !== id)) {
adapter.log.debug('Won\'t control ' + id + ' as not a color temperature.');
if (callback) callback('Not controlling');
return;
}
const unit = obj.common.unit;
let friendlyUnit;
if (unit == null) {
// default to mired
friendlyUnit = 'mired';
} else if (unit.match('K')) {
friendlyUnit = 'kelvin';
} else if (unit === '%') {
friendlyUnit = 'percent';
}
// see controlColorTemperature for a description
const minK = 2200;
const maxK = 7000;
let minValue = minK;
let maxValue = maxK;
switch (friendlyUnit) {
case 'kelvin': {
delta = delta * -1;
if (obj.common.min != null) minValue = parseFloat(obj.common.min);
if (obj.common.max != null) maxValue = parseFloat(obj.common.max);
break;
}
case 'mired': {
minValue = Math.round(1e6 / maxValue);
maxValue = Math.round(1e6 / minValue);
if (obj.common.min != null) minValue = parseFloat(obj.common.min);
if (obj.common.max != null) maxValue = parseFloat(obj.common.max);
break;
}
case 'percent': {
minValue = 0;
maxValue = 100;
}
}
adapter.getForeignState(id, function (err, state) {
let value = state ? state.val || 0 : 0;
// percentage of the state's range that is added/subtracted from the current value
const deltaFactor = 0.25;
value = Math.round(value + delta * deltaFactor * (maxValue - minValue));
value = Math.max(minValue, Math.min(value, maxValue));
adapter.log.debug('Controlling: ' + id + ' setting to ' + value);
adapter.setForeignState(id, value, function (err) {
if (err) adapter.log.error('Cannot switch device: ' + err);
if (callback) {
// convert value back to Kelvin for reporting
if (friendlyUnit === 'percent') {
// back to mired
const minM = 1e6 / maxK;
const maxM = 1e6 / minK;
value = minM + value / 100 * (maxM - minM);
}
if (friendlyUnit === 'mired' || friendlyUnit === 'percent') {
// back to Kelvin
value = Math.round(1e6 / value);
}
callback(null, value);
}
});
});
});
}
function findRoleInChannel(channel, role) {
for (let i = 0; i < channel.length; i++) {
let dev = channel[i];
if (dev.role === role){
return dev.id;
}
}
return null;
}
this.process = function (request, isEnabled, callback) {
let channels = {};
let count=0;
let device;
if (!isEnabled) {
request.header.name = 'NotSupportedInCurrentModeError';
callback({
header: request.header,
payload: {}
});
request = null;
return;
}
if (request && request.payload && request.payload.appliance && request.payload.appliance.additionalApplianceDetails) {
let details = null;
for (let i = 0; i < smartDevices.length; i++) {
if (smartDevices[i].applianceId === request.payload.appliance.applianceId) {
device = smartDevices[i];
details = smartDevices[i].additionalApplianceDetails;
}
}
adapter.log.debug(JSON.stringify(details));
if (details) {
if (details.group) {
channels = details.channels;
} else {
let id = details.id;
let role = details.role;
let _parts = id.split('.');
_parts.pop();
let channel = _parts.join('.');
channels[channel] = [{id: id, role: role}];
}
}
for (const chan in channels) {
count += channels[chan].length;
}
}
adapter.log.debug('New Request: ' + request.header.name);
switch (request.header.name) {
case 'DiscoverAppliancesRequest': {
//{
// "header": {
// "messageId": "6d6d6e14-8aee-473e-8c24-0d31ff9c17a2",
// "name": "DiscoverAppliancesRequest",
// "namespace": "Alexa.ConnectedHome.Discovery",
// "payloadVersion": "2"
// },
// "payload": {
// "accessToken": "*OAuth Token here*"
// }
//}
request.header.name = 'DiscoverAppliancesResponse';
//if (smartDevices.length > 100) smartDevices.splice(50, smartDevices.length - 50);
//console.log(JSON.stringify(smartDevices));
let smartDevicesCopy = JSON.parse(JSON.stringify(smartDevices));
for (let j = 0; j < smartDevicesCopy.length; j++) {
if (!smartDevicesCopy[j].additionalApplianceDetails) continue;
if (smartDevicesCopy[j].additionalApplianceDetails.names !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.names;
if (smartDevicesCopy[j].additionalApplianceDetails.name !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.name;
if (smartDevicesCopy[j].additionalApplianceDetails.byON !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.byON;
if (smartDevicesCopy[j].additionalApplianceDetails.byONs !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.byONs;
if (smartDevicesCopy[j].additionalApplianceDetails.nameModified !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.nameModified;
if (smartDevicesCopy[j].additionalApplianceDetails.room !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.room;
if (smartDevicesCopy[j].additionalApplianceDetails.func !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.func;
if (smartDevicesCopy[j].additionalApplianceDetails.smartType !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.smartType;
if (smartDevicesCopy[j].additionalApplianceDetails.smartTypes !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.smartTypes;
if (smartDevicesCopy[j].additionalApplianceDetails.channels !== undefined) delete smartDevicesCopy[j].additionalApplianceDetails.channels;
}
let response = {
header: request.header,
payload: {
discoveredAppliances: smartDevicesCopy/*[
{
"applianceId": "hm-rpc",
"manufacturerName": "ioBroker",
"modelName": "Bad.Hauptlicht.Aktor.STATE",
"version": "1",
"friendlyName": "Licht im Bad",
"friendlyDescription": "Bad Hauptlicht Aktor STATE",
"isReachable": true,
"actions": [
"incrementTargetTemperature",
"decrementTargetTemperature",
"setTargetTemperature"
],
"additionalApplianceDetails": {
id: 'hm-rpc.1.1'
}
},{
"applianceId": "hm-rpc-1",
"manufacturerName": "ioBroker",
"modelName": "Bad.Hauptlicht.Aktor.STATE",
"version": "1",
"friendlyName": "Licht im Bad",
"friendlyDescription": "Bad Hauptlicht Aktor STATE",
"isReachable": true,
"actions": [
"incrementTargetTemperature",
"decrementTargetTemperature",
"setTargetTemperature"
],
"additionalApplianceDetails": {}
},
{
"applianceId": "uniqueThermostatDeviceId",
"manufacturerName": "yourManufacturerName",
"modelName": "fancyThermostat",
"version": "your software version number here.",
"friendlyName": "Bedroom Thermostat",
"friendlyDescription": "descriptionThatIsShownToCustomer",
"isReachable": true,
"actions": [
"incrementTargetTemperature",
"decrementTargetTemperature",
"setTargetTemperature"
],
"additionalApplianceDetails": {
}
},
{
"actions": [
"incrementPercentage",
"decrementPercentage",
"setPercentage",
"turnOn",
"turnOff"
],
"additionalApplianceDetails": {},
"applianceId": "uniqueLightDeviceId",
"friendlyDescription": "descriptionThatIsShownToCustomer",
"friendlyName": "Living Room",
"isReachable": true,
"manufacturerName": "yourManufacturerName",
"modelName": "fancyLight",
"version": "your software version number here."
}
]*/
}
};
callback(response);
//console.log(JSON.stringify(response, null, 2));
request = null;
smartDevicesCopy = null;
}
break;
case 'TurnOnRequest': {
// {
// "header": {
// "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688",
// "name": "TurnOnRequest",
// "namespace": "Alexa.ConnectedHome.Control",
// "payloadVersion": "2"
// },
// "payload": {
// "accessToken": "[OAuth Token here]",
// "appliance": {
// "additionalApplianceDetails": {},
// "applianceId": "[Device ID for Ceiling Fan]"
// }
// }
// }
adapter.log.debug('ALEXA ON: ' + request.payload.appliance.applianceId);
for (let channel in channels) {
let toggle = findRoleInChannel(channels[channel], 'level.dimmer');
if (toggle) {
let byon = (device.additionalApplianceDetails.group) ?
device.additionalApplianceDetails.byONs[toggle] :
device.additionalApplianceDetails.byON;
if (!byon || byon === '-') toggle = null;
}
if (!toggle) toggle = findRoleInChannel(channels[channel], 'switch');
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling: ' + id);
controlOnOff(toggle, id, true, Object.keys(channels).length === 1, function (err) {
if (err) adapter.log.error('Cannot controlOnOff: ' + err);
if (!--count) {
request.header.name = 'TurnOnConfirmation';
callback({
header: request.header,
payload: {}
});
request = null;
}
});
}
}
writeResponse(request.payload.appliance.applianceId, 'ONOFF', true);
}
break;
case 'TurnOffRequest': {
// {
// "header": {
// "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688",
// "name": "TurnOffRequest",
// "namespace": "Alexa.ConnectedHome.Control",
// "payloadVersion": "2"
// },
// "payload": {
// "accessToken": "[OAuth Token here]",
// "appliance": {
// "additionalApplianceDetails": {},
// "applianceId": "[Device ID for Ceiling Fan]"
// }
// }
// }
adapter.log.debug('ALEXA OFF: ' + request.payload.appliance.applianceId);
for (let channel in channels) {
let toggle = findRoleInChannel(channels[channel], 'level.dimmer');
if (toggle) {
let byon = (device.additionalApplianceDetails.group) ?
device.additionalApplianceDetails.byONs[toggle] :
device.additionalApplianceDetails.byON;
if (!byon || byon === '-') toggle = null;
}
if (!toggle) toggle = findRoleInChannel(channels[channel], 'switch');
adapter.log.debug(toggle);
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling off: ' + id);
controlOnOff(toggle, id, false, Object.keys(channels).length === 1, function (err) {
if (err) adapter.log.error('Cannot controlOnOff: ' + err);
if (!--count) {
request.header.name = 'TurnOffConfirmation';
callback({
header: request.header,
payload: {}
});
request = null;
}
});
}
}
writeResponse(request.payload.appliance.applianceId, 'ONOFF', false);
}
break;
case 'SetLockStateRequest': {
// {
// "header": {
// "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688",
// "name": "TurnOnRequest",
// "namespace": "Alexa.ConnectedHome.Control",
// "payloadVersion": "2"
// },
// "payload": {
// "accessToken": "[OAuth Token here]",
// "appliance": {
// "additionalApplianceDetails": {},
// "applianceId": "[Device ID for Ceiling Fan]"
// }
// }
// }
adapter.log.debug('ALEXA LOCK: ' + request.payload.appliance.applianceId);
for (let channel in channels) {
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling lock: ' + id);
controlLock(id, Object.keys(channels).length === 1, function (err) {
if (err) adapter.log.error('Cannot controlLock: ' + err);
if (!--count) {
request.header.name = 'SetLockStateConfirmation';
if (err) {
callback({
header: request.header,
payload: {
lockState: 'UNLOCKED'
}
});
} else {
callback({
header: request.header,
payload: {
lockState: 'LOCKED'
}
});
}
request = null;
}
});
}
}
writeResponse(request.payload.appliance.applianceId, 'ONOFF', true);
}
break;
case 'GetLockStateRequest': {
// {
// "header": {
// "messageId": "01ebf625-0b89-4c4d-b3aa-32340e894688",
// "name": "TurnOnRequest",
// "namespace": "Alexa.ConnectedHome.Control",
// "payloadVersion": "2"
// },
// "payload": {
// "accessToken": "[OAuth Token here]",
// "appliance": {
// "additionalApplianceDetails": {},
// "applianceId": "[Device ID for Ceiling Fan]"
// }
// }
// }
adapter.log.debug('ALEXA GetLOCK: ' + request.payload.appliance.applianceId);
let result = null;
for (let channel in channels) {
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling lock: ' + id);
getLock(id, Object.keys(channels).length === 1, function (err, value, ts) {
if (err) adapter.log.error('Cannot getLock: ' + err);
if (result === null || value === false) {
adapter.log.debug('Settings result: ' + value);
result = value;
}
if (!--count) {
request.header.name = 'GetLockStateResponse';
if (err) {
callback({
header: request.header,
payload: {
lockState: 'UNLOCKED'
}
});
} else {
callback({
header: request.header,
payload: {
lockState: value ? 'LOCKED' : 'UNLOCKED',
applianceResponseTimestamp: new Date(ts).toISOString()
}
});
}
request = null;
}
});
}
}
}
break;
case 'SetPercentageRequest': {
adapter.log.debug('ALEXA Percent: ' + request.payload.appliance.applianceId + ' ' + request.payload.percentageState.value + '%');
for (let channel in channels) {
let dimmer = findRoleInChannel(channels[channel], 'level.dimmer');
adapter.log.debug('DATA: ' + JSON.stringify(channels[channel]));
adapter.log.debug(dimmer);
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling percentage: ' + id);
controlPercent(dimmer, id, request.payload.percentageState.value, Object.keys(channels).length === 1, function (err) {
if (err) adapter.log.error('Cannot controlPercent: ' + err);
if (!--count) {
request.header.name = 'SetPercentageConfirmation';
callback({
header: request.header,
payload: {}
});
request = null;
}
});
}
}
writeResponse(request.payload.appliance.applianceId, '%', request.payload.percentageState.value);
}
break;
case 'IncrementPercentageRequest': {
adapter.log.debug('ALEXA Increment: ' + request.payload.appliance.applianceId + ' ' + request.payload.deltaPercentage.value + '%');
for (let channel in channels) {
let dimmer = findRoleInChannel(channels[channel], 'level.dimmer');
adapter.log.debug('DATA: ' + JSON.stringify(channels[channel]));
adapter.log.debug(dimmer);
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling percentage: ' + id);
controlDelta(dimmer, id, request.payload.percentageState.value, Object.keys(channels).length === 1, function (err) {
if (err) adapter.log.error('Cannot controlDelta: ' + err);
if (!--count) {
request.header.name = 'IncrementPercentageConfirmation';
callback({
header: request.header,
payload: {}
});
request = null;
}
});
}
}
writeResponse(request.payload.appliance.applianceId, '<>', request.payload.deltaPercentage.value);
}
break;
case 'DecrementPercentageRequest': {
adapter.log.debug('ALEXA decrement: ' + request.payload.appliance.applianceId + ' ' + request.payload.deltaPercentage.value + '%');
for (let channel in channels) {
let dimmer = findRoleInChannel(channels[channel], 'level.dimmer');
adapter.log.debug('DATA: ' + JSON.stringify(channels[channel]));
adapter.log.debug(dimmer);
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling percentage: ' + id);
controlDelta(dimmer, id, request.payload.percentageState.value * (-1), Object.keys(channels).length === 1, function (err) {
if (err) adapter.log.error('Cannot controlDelta: ' + err);
if (!--count) {
request.header.name = 'DecrementPercentageConfirmation';
callback({
header: request.header,
payload: {}
});
request = null;
}
});
}
}
writeResponse(request.payload.appliance.applianceId, '<>', (-1) * request.payload.deltaPercentage.value);
}
break;
case 'SetTargetTemperatureRequest': {
adapter.log.debug('ALEXA temperature Percent: ' + request.payload.appliance.applianceId + ' ' + request.payload.targetTemperature.value + ' grad');
for (let channel in channels) {
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling percentage: ' + id);
controlTemperature(id, request.payload.targetTemperature.value, Object.keys(channels).length === 1, function (err, response) {
if (err) adapter.log.error('Cannot controlTemperature: ' + err);
if (!--count) {
if (err || !response) {
response = {
payload: {
targetTemperature: {
value: 0
},
/*temperatureMode: {
value: 'AUTO'
},*/
previousState: {
targetTemperature: {
value: 0
},
mode: {
value: 'AUTO'
}
}
}
};
}
request.header.name = 'SetTargetTemperatureConfirmation';
response.header = request.header;
callback(response);
request = null;
}
});
}
}
}
break;
case 'IncrementTargetTemperatureRequest': {
request.payload.deltaTemperature.value = request.payload.deltaTemperature.value || 1;
adapter.log.debug('ALEXA temperature Increment: ' + request.payload.appliance.applianceId + ' ' + request.payload.deltaTemperature.value + ' grad');
for (let channel in channels) {
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling percentage: ' + id);
controlTemperatureDelta(id, request.payload.deltaTemperature.value, Object.keys(channels).length === 1, function (err, response) {
if (err) adapter.log.error('Cannot controlTemperatureDelta: ' + err);
if (!--count) {
if (err || !response) {
response = {
payload: {
targetTemperature: {
value: 0
},
/*temperatureMode: {
value: 'AUTO'
},*/
previousState: {
targetTemperature: {
value: 0
},
mode: {
value: 'AUTO'
}
}
}
};
}
request.header.name = 'IncrementTargetTemperatureConfirmation';
response.header = request.header;
callback(response);
request = null;
}
});
}
}
}
break;
case 'DecrementTargetTemperatureRequest': {
request.payload.deltaTemperature.value = request.payload.deltaTemperature.value || 1;
adapter.log.debug('ALEXA temperature decrement: ' + request.payload.appliance.applianceId + ' ' + request.payload.deltaTemperature.value + ' grad');
for (let channel in channels) {
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling percentage: ' + id);
controlTemperatureDelta(id, request.payload.deltaTemperature.value * (-1), Object.keys(channels).length === 1, function (err, response) {
if (err) adapter.log.error('Cannot controlTemperatureDelta: ' + err);
if (!--count) {
if (err || !response) {
response = {
payload: {
targetTemperature: {
value: 0
},
/*temperatureMode: {
value: 'AUTO'
},*/
previousState: {
targetTemperature: {
value: 0
},
mode: {
value: 'AUTO'
}
}
}
};
}
request.header.name = 'DecrementTargetTemperatureConfirmation';
response.header = request.header;
callback(response);
request = null;
}
});
}
}
}
break;
case 'GetTemperatureReadingRequest': {
adapter.log.debug('ALEXA temperature get: ' + request.payload.appliance.applianceId);
let values = 0;
let num = 0;
for (let channel in channels) {
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling percentage: ' + id);
getTemperature(id, Object.keys(channels).length === 1, function (err, value, ts) {
if (err) adapter.log.error('Cannot getTemperature: ' + err);
if (value){
num++;
values += value;
}
if (!--count) {
request.header.name = 'GetTemperatureReadingResponse';
if (err) {
callback({
header: request.header,
payload: {
temperatureReading: {
value: 0
},
applianceResponseTimestamp: new Date().toISOString()
}
});
} else {
callback({
header: request.header,
payload: {
temperatureReading: {
value: Math.round(values * 10 / num) / 10
},
applianceResponseTimestamp: new Date(ts).toISOString()
}
});
}
request = null;
}
});
}
}
}
break;
case 'GetTargetTemperatureRequest': {
adapter.log.debug('ALEXA TargetTemperature get: ' + request.payload.appliance.applianceId);
let result;
for (let channel in channels) {
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Getting: ' + id);
getTargetTemperature(id, Object.keys(channels).length === 1, function (err, value, ts) {
if (err) adapter.log.error('Cannot getTargetTemperature: ' + err);
if (!result && value) result = value;
if (!--count) {
request.header.name = 'GetTargetTemperatureResponse';
adapter.log.debug('Got: ' + value);
callback({
header: request.header,
payload: {
targetTemperature: {
value: result
},
temperatureMode: {
value: 'CUSTOM',
friendlyName: ''
},
applianceResponseTimestamp: new Date(ts).toISOString()
}
});
request = null;
}
});
}
}
}
break;
case 'SetColorRequest': {
adapter.log.debug('ALEXA Color: ' + request.payload.appliance.applianceId + ' ' + JSON.stringify(request.payload.color));
let doneCallback = function (err) {
if (err) adapter.log.error('Cannot SetColorRequest: ' + err);
adapter.log.debug(' Hue Count: ' + count);
if (!--count) {
adapter.log.debug(' Done Count: ' + count);
request.header.name = 'SetColorConfirmation';
callback({
header: request.header,
payload: {
achievedState: {
color: JSON.parse(JSON.stringify(request.payload.color))
}
}
});
request = null;
}
};
count = Object.keys(channels).length;
for (let channel in channels) {
let hue = findRoleInChannel(channels[channel], 'level.color.hue');
if (hue) {
adapter.log.debug(' Using HUE for: ' + channel);
let sat = findRoleInChannel(channels[channel], 'level.color.saturation');
let bri = findRoleInChannel(channels[channel], 'level.dimmer');
let color = JSON.parse(JSON.stringify(request.payload.color));
controlColorHue(sat, hue, bri, color, Object.keys(channels).length === 1, doneCallback);
continue;
}
let rgb = findRoleInChannel(channels[channel], 'level.color.rgb');
if (rgb) {
adapter.log.debug(' Using RGB for: ' + channel);
let color = JSON.parse(JSON.stringify(request.payload.color));
controlColorRgb(rgb, color, Object.keys(channels).length === 1, doneCallback);
continue;
}
adapter.log.debug(' Unable to set color for: ' + channel);
adapter.log.debug('Count: ' + count);
doneCallback();
}
//writeResponse(request.payload.appliance.applianceId, '%', request.payload.percentageState.value);
}
break;
case 'SetColorTemperatureRequest': {
for (let channel in channels) {
let temperature = findRoleInChannel(channels[channel], 'level.color.temperature');
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling temperature kelvin: ' + id);
controlColorTemperature(temperature, id, request.payload.colorTemperature.value, Object.keys(channels).length === 1, function (err) {
if (err) adapter.log.error('Cannot controlColorTemperature: ' + err);
if (!--count) {
request.header.name = 'SetColorTemperatureConfirmation';
adapter.log.debug('DONE: ' + id);
callback({
header: request.header,
payload: {
achievedState: {
colorTemperature: {
value: request.payload.colorTemperature.value
}
}
}
});
request = null;
}
});
}
}
}
break;
case 'IncrementColorTemperatureRequest': {
for (let channel in channels) {
let temperature = findRoleInChannel(channels[channel], 'level.color.temperature');
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling temperature kelvin: ' + id);
controlColorTemperatureDelta(temperature, id, -1, Object.keys(channels).length === 1, function (err, res) {
if (err) adapter.log.error('Cannot controlColorTemperatureDelta: ' + err);
adapter.log.debug('Count: ' + count);
if (!--count) {
request.header.name = 'IncrementColorTemperatureConfirmation';
adapter.log.debug('DONE: ' + id);
callback({
header: request.header,
payload: {
achievedState: {
colorTemperature: {
value: res
}
}
}
});
request = null;
}
});
}
}
}
break;
case 'DecrementColorTemperatureRequest': {
for (let channel in channels) {
let temperature = findRoleInChannel(channels[channel], 'level.color.temperature');
for (let i = 0; i < channels[channel].length; i++) {
let id = channels[channel][i].id;
adapter.log.debug('Controlling temperature kelvin: ' + id);
controlColorTemperatureDelta(temperature, id, 1, Object.keys(channels).length === 1, function (err, res) {
if (err) adapter.log.error('Cannot controlColorTemperatureDelta: ' + err);
adapter.log.debug('Count: ' + count);
if (!--count) {
request.header.name = 'DecrementColorTemperatureConfirmation';
adapter.log.debug('DONE: ' + id);
callback({
header: request.header,
payload: {
achievedState: {
colorTemperature: {
value: res
}
}
}
});
request = null;
}
});
}
}
}
break;
case 'HealthCheckRequest': {
request.header.name = 'HealthCheckResponse';
try {
adapter.log.debug('HealthCheckRequest duration: ' + (new Date().getTime() - request.payload.initiationTimestamp) + ' ms');
} catch (e) {
adapter.log.error('No payload');
}
callback({
header: request.header,
payload: {
description: 'Das System ist OK', // ?? What about english?
isHealthy: true
}
});
request = null;
}
break;
default: {
request.header.name = 'NotSupportedInCurrentModeError';
callback({
header: request.header,
payload: {}
});
request = null;
}
break;
}
};
this.updateDevices = function (callback) {
this.getDevices(function (err, result) {
smartDevices = result;
callback && callback();
});
};
this.getEnums = function () {
return enums;
};
this.getDevices = function (callback) {
if (!callback) return smartDevices;
adapter.objects.getObjectView('system', 'state', {}, function (err, _states) {
let states = {};
let ids = [];
let alexaIds = [];
let groups = {};
let names = {};
enums = [];
if (_states && _states.rows) {
for (let i = 0; i < _states.rows.length; i++) {
if (_states.rows[i].value) {
states[_states.rows[i].id] = _states.rows[i].value;
ids.push(_states.rows[i].id);
if (adapter.config.noCommon) {
if (_states.rows[i].value.common &&
_states.rows[i].value.common.custom &&
_states.rows[i].value.common.custom[adapter.namespace] &&
_states.rows[i].value.common.custom[adapter.namespace].smartName &&
_states.rows[i].value.common.custom[adapter.namespace].smartName !== 'ignore') {
alexaIds.push(_states.rows[i].id);
}
} else {
if (_states.rows[i].value.common &&
_states.rows[i].value.common.smartName &&
_states.rows[i].value.common.smartName !== 'ignore') {
alexaIds.push(_states.rows[i].id);
}
}
}
}
}
ids.sort();
adapter.objects.getObjectView('system', 'enum', {}, function (err, doc) {
// Build overlap from rooms and functions
let rooms = [];
let funcs = [];
let smartName;
if (doc && doc.rows) {
for (let i = 0, l = doc.rows.length; i < l; i++) {
if (doc.rows[i].value) {
let _id = doc.rows[i].id;
smartName = getSmartName(doc.rows[i].value);
if (_id.match(/^enum\.rooms\./) && smartName !== 'ignore' && smartName !== false) {
rooms.push(doc.rows[i].value);
}
if (_id.match(/^enum\.functions\./) && smartName !== 'ignore' && smartName !== false) {
funcs.push(doc.rows[i].value);
}
if (_id.match(/^enum\.rooms\./) || _id.match(/^enum\.functions\./)) {
enums.push({
id: _id,
name: doc.rows[i].value.common.name,
smartName: smartName
});
}
}
}
}
let result = [];
for (let f = 0; f < funcs.length; f++) {
if (!funcs[f].common || !funcs[f].common.members || typeof funcs[f].common.members !== 'object' || !funcs[f].common.members.length) continue;
for (let s = 0; s < funcs[f].common.members.length; s++) {
let id = funcs[f].common.members[s];
smartName = getSmartName(funcs[f]);
if (smartName && typeof smartName === 'object') smartName = smartName[lang] || smartName.en;
let func = smartName || funcs[f].common.name;
if (!func) {
func = funcs[f]._id.substring('enum.functions.'.length);
func = func[0].toUpperCase() + func.substring(1);
}
// Find room
let room = '';
for (let r = 0; r < rooms.length; r++) {
if (!rooms[r].common || !rooms[r].common.members || typeof rooms[r].common.members !== 'object' || !rooms[r].common.members.length) continue;
if (rooms[r].common.members.indexOf(id) !== -1) {
smartName = getSmartName(rooms[r]);
if (smartName && typeof smartName === 'object') smartName = smartName[lang] || smartName.en;
room = smartName || rooms[r].common.name;
if (!room) {
room = rooms[r]._id.substring('enum.rooms.'.length);
room = room[0].toUpperCase() + room.substring(1);
}
}
if (!room) {
// may be the channel is in the room
let _parts = id.split('.');
_parts.pop();
let channel = _parts.join('.');
if (rooms[r].common.members.indexOf(channel) !== -1) {
smartName = getSmartName(funcs[f]);
if (smartName && typeof smartName === 'object') smartName = smartName[lang] || smartName.en;
room = smartName || rooms[r].common.name;
if (!room) {
room = rooms[r]._id.substring('enum.rooms.'.length);
room = room[0].toUpperCase() + room.substring(1);
}
}
}
if (room) break;
}
if (!states[id]) {
let m = new RegExp('^' + id.replace(/\./g, '\\.'));
for (let ii = 0; ii < ids.length; ii++) {
if (ids[ii] < id) continue;
if (m.exec(ids[ii])) {
if (states[ids[ii]].common.role && (
states[ids[ii]].common.role === 'state' ||
states[ids[ii]].common.role.match(/^switch/) ||
states[ids[ii]].common.role.match(/^level/)
)) {
processState(states, ids[ii], room, func, alexaIds, groups, names, result);
}
continue;
}
break;
}
} else {
processState(states, id, room, func, alexaIds, groups, names, result);
}
}
}
// process states with defined smartName
for (let j = 0; j < alexaIds.length; j++) {
processState(states, alexaIds[j], null, null, null, groups, names, result);
}
result.sort(function (a, b) {
if (a.friendlyName > b.friendlyName) return 1;
if (a.friendlyName < b.friendlyName) return -1;
return 0;
});
for (let k = result.length - 1; k >= 0; k--) {
if (result[k].disabled) {
result.splice(k, 1);
} else {
adapter.log.debug('Created ALEXA device: ' + result[k].friendlyName + ' ' + JSON.stringify(result[k].actions));
}
}
callback(err, result);
});
});
}
}
module.exports = AlexaSH2;