Hallo zusammen,
und hier das Nikolausgeschenk von mir. Funktionalität ist wieder vollumfänglich enthalten.
PLUS: Jetzt werden auch Szenarien unterstützt. Die sind im Objektbaum unter 'actionGroups' zu finden.
Viele Grüße
var request = require('request');
// enable debugging mode
//request.debug = true;
var lastEventTime = new Date().getTime();
var rawDeviceData = false;
var tahomaJar = request.jar();
var baseRequest = request.defaults({
headers: {
'User-Agent': 'Mozilla/5.0 (Macintosh; Intel Mac OS X 10_7_5) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/49.0.2623.112 Safari/537.36'},
jar: tahomaJar
});
var tahomaDevices = {};
var tahomaActionGroups = {};
var Map_DeviceURL2StateName = {};
var userName = "xxxUSERNAMExxx";
var passWord = "xxxPASSWORTxxx";
var baseURL = 'https://www.tahomalink.com/enduser-mobile-web/enduserAPI/';
var isConnectedInternal = false;
let loginInProgress = false;
var eventRegisterID = '-1';
createState('tahoma.connected', false, {
read: true,
write: true,
name: "connection status of tahoma",
type: "boolean",
def: false
});
createOrUpdateState('tahoma.update', false, {
read: true,
write: true,
role: "button"
});
on('javascript.0.tahoma.update', function(obj)
{
//log("tahoma refresh states requested: " + obj);
if (obj.newState.val)
{
getAllStates();
}
});
function isConnected()
{
return isConnectedInternal;
}
function setConnected(connected)
{
isConnectedInternal = connected;
setState('javascript.0.tahoma.connected', connected, true);
}
function getCreateStateOptions4Widget(widget)
{
if (widget === 'PositionableRollerShutter')
{
return {
"role": "blind",
};
}
if (widget === 'LuminanceSensor')
{
return {
"role": "sensor"
};
}
return {
read: true,
write: false,
role: "state"
};
}
function getCreateStateOptions4State(widget, stateName)
{
if (stateName === "core:ClosureState" || stateName === "core:TargetClosureState")
{
return {
"type": "number", // optional, default "number"
"read": true, // mandatory, default true
"write": true, // mandatory, default true
"min": 0, // optional, default 0
"max": 100, // optional, default 100
"unit": "%", // optional, default %
"role": "level.blind" // mandatory
};
}
if (stateName === "core:SlateOrientationState")
{
return {
"type": "number", // optional, default "number"
"read": true, // mandatory, default true
"write": true, // mandatory, default true
"min": 0, // optional, default 0
"max": 100, // optional, default 100
"unit": "%", // optional, default %
"role": "level.blind.orientation" // mandatory
};
}
if (stateName === "core:LuminanceState")
{
return {
"type": "number", // optional, default "number"
"read": true, // mandatory, default true
"write": false, // mandatory, default true
"min": 0, // optional, default 0
"max": 100000, // optional, default 100
"unit": "Lux", // optional, default %
"role": "level.color.luminance" // mandatory
};
}
return {
read: true,
write: false,
role: "state"
};
}
function sendPOST(requestPath, payload, callback)
{
login(function(err,data)
{
if (err)
{
return callback(err, data);
}
sendInternalPOST(requestPath, payload, callback);
});
}
function sendGET(requestPath, payload, callback)
{
login(function(err,data)
{
if (err)
{
return callback(err, data);
}
sendInternalGET(requestPath, payload, callback);
});
}
function sendInternalGET(requestPath, payload, callback){
var url = baseURL + requestPath;
var formPayload = null;
var jsonPayload = null;
jsonPayload = payload;
log("perform " + requestPath + " with payload:" + JSON.stringify(payload), 'debug');
baseRequest.get(
{
url: url,
json: jsonPayload,
form: formPayload
},
function(error, response, body)
{
if (!error && response.statusCode === 200)
{
callback(false, body);
}
else if (response && (response.statusCode === 401 || response.statusCode === 403))
{
log("error during tahomalink request: " + response.statusText + " ->" + response.statusCode + " retry " + requestPath, 'error');
// session expired?
setConnected(false);
loginInProgress = false;
// perform login and send again
sendGET(requestPath, payload, callback);
}
else
{
log("error during tahomalink request: " + response.statusCode + ": " + error +
", request path: " + requestPath + " with payload:" + JSON.stringify(payload), 'error');
var result = {};
result.error = error;
if(typeof response !== "undefined")
{
log("response status: " + response.statusCode + " " + response.statusText,'debug');
result.responseStatusCode = response.statusCode;
result.responseStatusText = response.statusText;
}
callback(true, result);
}
});
}
function sendInternalPOST(requestPath, payload, callback)
{
var url = baseURL + requestPath;
if (requestPath.endsWith("apply"))
{
url = baseURL + requestPath + "/highPriority";
}
var formPayload = null;
var jsonPayload = null;
if (requestPath === 'login')
{
formPayload = payload;
}
else
{
jsonPayload = payload;
}
log("perform " + requestPath + " with payload:" + JSON.stringify(payload), 'debug');
baseRequest.post(
{
url: url,
json: jsonPayload,
form: formPayload
},
function(error, response, body)
{
if (!error && response.statusCode === 200)
{
if (requestPath === 'login')
{
callback(false, JSON.parse(body));
}
else
{
callback(false, body);
}
}
else if (response && requestPath !== 'logout'
&& (response.statusCode === 401 || response.statusCode === 403))
{
log("error during tahomalink request: " + response.statusText + " ->" + response.statusCode + " retry " + requestPath, 'error');
// session expired?
setConnected(false);
loginInProgress = false;
// perform login and send again
sendPOST(requestPath, payload, callback);
}
else
{
log("error during tahomalink request: " + response.statusCode + ": " + error +
", request path: " + requestPath + " with payload:" + JSON.stringify(payload), 'error');
var result = {};
result.error = error;
if(typeof response !== "undefined")
{
log("response status: " + response.statusCode + " " + response.statusText,'debug');
result.responseStatusCode = response.statusCode;
result.responseStatusText = response.statusText;
}
callback(true, result);
}
});
}
function logout(callback)
{
var performLogout = isConnected();
setConnected(false);
if (performLogout)
{
sendInternalPOST("logout", {}, function (err, data)
{
callback(err, data);
});
}
else
{
callback(false, {});
}
}
function login(callback)
{
if (isConnected())
{
callback(false, {});
return;
}
// check for login already started but not yet finished
if (loginInProgress)
{
//log("login in progress, wait 100 msec");
setTimeout(function()
{
login(callback);
}, 1500);
return;
}
loginInProgress = true;
var payload = {'userId': userName, 'userPassword': passWord};
var result = sendInternalPOST("login", payload, function (err, data)
{
if (err || !data.success)
{
loginInProgress = false;
return callback(true, data);
}
lastEventTime = new Date().getTime();
setConnected(true);
loginInProgress = false;
getUserInfo(function (err,data)
{
if (!err)
{
return getSetup(callback);
}
callback(err, data);
});
});
}
function getUserInfo(callback)
{
sendGET('enduser/mainAccount', {},function (err, data)
{
if (!err)
{
updateData('userdata', data.endUser);
callback(false, data);
}
else
{
log("enduser/mainAccount failed!", 'error');
}
});
}
function updateGateWayData(gateways)
{
for (var i in gateways)
{
var gateway = gateways[i];
updateData(gateway.gatewayId, gateway);
}
}
function updateDevices(devices)
{
tahomaDevices = devices;
for (var i in devices)
{
var device = devices[i];
// just set the raw data from tahoma
updateDevice('devices.' + device.label, device);
}
}
function updateDevice(name, deviceData)
{
createOrUpdateState('tahoma.' + name, '',
getCreateStateOptions4Widget(deviceData.widget));
// device URL
createOrUpdateState('tahoma.' + name + '.deviceURL', deviceData.deviceURL);
// states
for (var stateKey in deviceData.states)
{
var state = deviceData.states[stateKey];
createOrUpdateState('tahoma.' + name + '.states.' + state.name,
mapValueTahoma2ioBroker(state.name, state.value),
getCreateStateOptions4State(deviceData.widget, state.name));
}
// commands
for (var commandKey in deviceData.definition.commands)
{
var command = deviceData.definition.commands[commandKey];
if (command.nparams === 0)
{
createOrUpdateState('tahoma.' + name + '.commands.' + command.commandName, false, {
read: true,
write: true,
role: "button"
});
}
}
// raw data
if (rawDeviceData)
{
for (var p in deviceData)
{
var value = deviceData[p];
if (typeof(value) === 'object')
{
updateData('raw.' + name + '.' + p, value);
}
else
{
createOrUpdateState('tahoma.raw.' + name + '.' + p, value);
}
}
}
}
function updateActionGroups(actionGroups)
{
tahomaActionGroups = actionGroups;
for (var i in actionGroups)
{
var actionGroup = actionGroups[i];
// just set the raw data from tahoma
updateActionGroup('actionGroups.' + actionGroup.label, actionGroup);
}
}
function updateActionGroup(actionGroup, actionGroupData)
{
// Action Group OID
createOrUpdateState('tahoma.' + actionGroup + '.oid', actionGroupData.oid);
createOrUpdateState('tahoma.' + actionGroup + '.commands.' + 'execute', false, {
read: true,
write: true,
role: "button"
});
}
function mapValueTahoma2ioBroker(stateName, stateValue)
{
if (stateName === 'core:ClosureState' ||
stateName === 'core:TargetClosureState' ||
stateName === "core:SlateOrientationState" ||
stateName === "core:LuminanceState"
)
{
stateValue = parseInt(stateValue,10);
}
return stateValue;
}
function mapValueioBroker2Tahoma(stateName, stateValue)
{
if (stateName === 'core:ClosureState' || stateName === 'core:TargetClosureState')
{
//stateValue = parseInt(stateValue,10);
}
return stateValue;
}
function updateData(type, data)
{
for (var p in data)
{
var value = data[p];
if (typeof(value) === 'object')
{
updateData(type + '.' + p, value);
}
else
{
createOrUpdateState('tahoma.' + type + '.' + p, value);
}
}
}
function createOrUpdateState(key, value, options)
{
key = key.replace(' ' , '_');
var state = getState("javascript.0." + key);
var typeName = "string";
if (value === "true" || value === "false")
{
value = value == "true";
typeName = "boolean";
}
else if (Number.isInteger(value))
{
value = parseInt(value,10);
typeName = "number";
}
else if (!isNaN(value))
{
value = Number(value);
typeName="number";
}
if (state.notExist)
{
if (typeof(options) === 'undefined')
{
options = {
read: true,
write: false,
type: typeName
};
}
// create state
createState(key, value, 0, options);
}
else
{
setState("javascript.0." + key, value, true);
}
}
function getSetup(callback)
{
sendGET('setup', {},function (err, data)
{
if (!err)
{
updateGateWayData(data.gateways);
updateData('location', data.location);
updateDevices(data.devices);
// delete old devices
deleteOldDevices();
// update mapping table device URL to state key with label
$('javascript.0.tahoma.devices.*.deviceURL').each(function (id, i)
{
var state = getState(id);
Map_DeviceURL2StateName[state.val] = id.substr(0, id.indexOf(".deviceURL"));
//log("set mapping deviceURL to name: " + state.val + " -> " + id.substr(0, id.indexOf(".deviceURL")),'debug');
});
// refresh
refresh(callback);
}
else
{
log("setup failed!",'error');
callback(err, {});
}
});
sendGET('actionGroups', {},function (err, data)
{
if (!err)
{
updateActionGroups(data);
}
else
{
log("actionGroups failed!",'error');
callback(err, {});
}
});
}
function refresh(callback){
sendPOST('/setup/devices/states/refresh', {}, function (err,data)
{
if (err)
{
log("refresh device state failed", 'error');
}
callback(err, {});
});
}
function refresh(){
sendPOST('/setup/devices/states/refresh', {}, function (err,data)
{
if (err)
{
log("refresh device state failed", 'error');
}
});
}
function getAllStates()
{
login(function (err, data)
{
if (err)
{
return;
}
});
if(eventRegisterID === '-1'){
sendPOST("events/register", {}, function (err,data)
{
if (err)
{
log("events/register failed", 'error');
return;
}
eventRegisterID = data.id;
log ("eventRegisterID = " + eventRegisterID);
fetchEvents();
});
}
else {
fetchEvents();
}
}
function fetchEvents(){
refresh();
sendPOST("events/" + eventRegisterID + "/fetch", {}, function (err,data)
{
if (err)
{
return;
}
var callback;
log ("events/" + eventRegisterID + "/fetch" + "Fetched Data" + data, 'debug');
updateDeviceStateFromEvent(data);
});
}
function updateDeviceStateFromEvent(events)
{
for (var i in events)
{
lastEventTime = new Date().getTime();
var event = events[i];
if (event.name === 'DeviceStateChangedEvent')
{
updateDeviceState(event);
}
}
}
function updateDeviceState(event)
{
var deviceURL = event.deviceURL;
var states = event.deviceStates;
var devicePath = Map_DeviceURL2StateName[deviceURL];
// tahoma.devices.XXX
// tahoma.devices.XXX.states.Y
log("got event for device " + devicePath, 'debug');
for (var i in event.deviceStates)
{
var state = event.deviceStates[i];
var name = state.name;
var value = mapValueTahoma2ioBroker(name, state.value);
log("found " + devicePath + '.states.' + name + " -> " + value, 'debug');
setState(devicePath + '.states.' + name, value, true);
}
}
function deleteOldDevices()
{
var currentTime = new Date().getTime();
$('javascript.0.tahoma.devices.*.lastUpdateTime').each(function (id, i)
{
var state = getState(id);
var device = id.substr(0, id.indexOf(".lastUpdateTime"));
if (currentTime - state.ts > 5 * 60 * 1000)
{
// update older than 1 minute -> drop
log ("found old " + device + " -> " + new Date(state.ts),'debug');
$(device + '.*').each(function (id, i)
{
log("delete state:" + id, 'debug');
deleteState(id);
});
}
});
}
on(/^javascript\.0\.tahoma\.devices.*\.commands/,function(obj)
{
if (obj.state.val)
{
var id = obj.id;
log("button pressed: " + id + " -> " + JSON.stringify(obj), 'debug');
var commandName = id.substr(id.lastIndexOf(".")+1);
var deviceURL = getState(id.substr(0, id.indexOf(".commands.")) + ".deviceURL").val;
var payload = {'label':'command ' + commandName + ' from ioBroker',
'actions':[{
'deviceURL': deviceURL,
'commands': [{
'name': commandName,
'parameters': []
}]}]};
sendPOST("exec/apply", payload, function(err, data)
{
// reset state
setState(obj.id, !obj.state.val);
});
}
});
on(/^javascript\.0\.tahoma\.devices.*\.states.core:ClosureState/,function(obj)
{
onClosureStateChange(obj);
});
on(/^javascript\.0\.tahoma\.devices.*\.states.core:TargetClosureState/,function(obj)
{
onClosureStateChange(obj);
});
function onClosureStateChange(obj)
{
if (!obj.newState.ack)
{
var id = obj.id;
//log("closure state changed: " + id + " -> " + JSON.stringify(obj));
var commandName = "setClosure";
var deviceURL = getState(id.substr(0, id.indexOf(".states.")) + ".deviceURL").val;
var stateValue = obj.newState.val;
var roomName = id.substr(id.indexOf('.devices.')+9);
roomName = roomName.substr(0,roomName.indexOf('.states'));
var payload = {'label': roomName + ' - Positioniere auf ' + stateValue + ' % - ioBroker',
'actions':[{
'deviceURL': deviceURL,
'commands': [{
'name': commandName,
'parameters': [ mapValueioBroker2Tahoma('core:ClosureState', stateValue) ]
}]}]};
sendPOST("exec/apply", payload, function(err, data)
{
// reset state
//setState(obj.id, !obj.state.val);
});
}
};
on(/^javascript\.0\.tahoma\.devices.*\.states.core:SlateOrientationState/,function(obj)
{
if (!obj.newState.ack)
{
var id = obj.id;
var commandName = "setOrientation";
var deviceURL = getState(id.substr(0, id.indexOf(".states.")) + ".deviceURL").val;
var stateValue = obj.newState.val;
var roomName = id.substr(id.indexOf('.devices.')+9);
roomName = roomName.substr(0,roomName.indexOf('.states'));
//log("slate orientation changed: " + roomName + " -> " + stateValue);
var payload = {'label':roomName + ' - Ausrichtung ' + stateValue + ' % - ioBroker',
'actions':[{
'deviceURL': deviceURL,
'commands': [{
'name': commandName,
'parameters': [ mapValueioBroker2Tahoma('core:SlateOrientationState', stateValue) ]
}]}]};
sendPOST("exec/apply", payload, function(err, data)
{
// reset state
//setState(obj.id, !obj.state.val);
});
}
});
on({id: /^javascript\.0\.tahoma\.actionGroups.*\.commands.execute/, valNe: false}, function (obj) {
if (obj.state.val)
{
var id = obj.id;
var oid = getState(id.substr(0, id.indexOf(".commands.")) + ".oid").val;
log(baseURL + "exec/" + oid);
sendPOST("exec/" + oid, "", function(err, data)
{
if (err)
{
log(baseURL + "exec/" + oid, "error");
return;
}
});
}
});
// if connected, update state all 5 seconds
//schedule("*/5 * * * * *", function ()
// if connected, update state all 10 seconds
schedule("*/10 * * * * *", function ()
{
// if connected, update states
if (isConnected())
{
getAllStates();
if (new Date().getTime() - lastEventTime > 5 * 60 * 1000)
{
// no events within last 60 seconds
logout(function () {});
}
}
});
// update state all 10 minutes
schedule("*/10 * * * *", function ()
{
if (new Date().getTime() - lastEventTime > 9 * 60 * 1000)
{
log("update tahoma all 10 minutes");
getAllStates();
}
});
onStop(function (callback) {
logout(function (err, data)
{
callback();
});
}, 10000 /*ms*/);
// start script
getAllStates();