iobroker.gardena
Version:
Gardena Smart System
717 lines (613 loc) • 23.5 kB
JavaScript
;
let adapter;
const jsonPath = require('jsonpath');
const gardena_commands = require(__dirname + '/gardena_commands.json'); // gardena commands
let dp_prefix; // prefix for datapoints in the objects DB
let loc_prefix; // prefix for locations in the objects DB
const conf_seperator = '___'; // separator to evaluate datapoints from the config
let conf_re_seperator = new RegExp(conf_seperator, 'g');
exports.setAdapter = function(adapter_in) {
adapter = adapter_in;
dp_prefix = 'gardena.' + adapter.instance + '.datapoints.';
loc_prefix = 'gardena.' + adapter.instance + '.locations.';
};
// getter
exports.getdp_prefix = function() {
return dp_prefix;
};
exports.getloc_prefix = function() {
return loc_prefix;
};
exports.getconf_seperator = function() {
return conf_seperator;
};
exports.getconf_re_seperator = function() {
return conf_re_seperator;
};
// create location's datapoints in the DB
function createLocationStateInDB(id, state, val) {
let crole = 'state';
let ctype = typeof(val);
switch(state) {
case 'authorized_at':
crole = 'date';
break;
case 'street':
crole = 'location';
break;
case 'city':
crole = 'location';
break;
case 'address':
crole = 'location';
break;
case 'latitude':
crole = 'value.gps.latitude';
break;
case 'longitude':
crole = 'value.gps.longitude';
break;
case 'sunrise':
crole = 'date.sunrise';
break;
case 'sunset':
crole = 'date.sunset';
break;
default:
switch(ctype) {
case 'string':
crole = 'text';
break;
case 'number':
crole = 'value';
break;
}
}
let obj = {
"_id": 'locations.' + id + '.' + state,
"type": "state",
"common": {
"name": state,
"read": true,
"write": false,
"type": ctype,
"role": crole
},
"native": {}
};
adapter.setObjectNotExists(id, obj, function(err) {
adapter.setState(id, val, true);
});
}
// update locations in database
exports.updateDBLocations = function (jsondata, callback) {
// update locations in the database
for(let ckey in jsondata) {
if (!(jsondata.hasOwnProperty(ckey) && ckey === 'locations')) break;
let locations = jsondata[ckey];
// go through all locations
for(let i=0;i<locations.length;i++) {
let curlocation = locations[i];
// retrieve location id
if(!curlocation.hasOwnProperty('id') || !curlocation['id'] || !curlocation.hasOwnProperty('name')) break; // valid location id?
// create location state
let obj = {
"_id": 'locations.' + curlocation['id'],
"type": "",
"common": {
"name": curlocation['name']
},
"native": {}
};
adapter.setObjectNotExists(loc_prefix + curlocation['id'], obj, function(err) {
// retrieve states from the DB for the current location
adapter.getStates(loc_prefix + curlocation['id'] + '.*', function (err, states) {
for(let cstate in curlocation) {
if(states.hasOwnProperty(loc_prefix + curlocation['id'] + '.' + cstate)) {
// states exists, set state value
} else {
// state does not exists, create it
if(Array.isArray(curlocation[cstate])) {
createLocationStateInDB(loc_prefix + curlocation['id'] + '.' + cstate, cstate, curlocation[cstate].join(','));
} else if(typeof curlocation[cstate] === 'object' && curlocation[cstate]) {
// create top group for the array
let obj = {
"_id": loc_prefix + curlocation['id'] + '.' + cstate,
"type": "",
"common": {
"name": cstate
},
"native": {}
};
adapter.setObjectNotExists(loc_prefix + curlocation['id'] + '.' + cstate, obj);
// go through the array
for(let citem in curlocation[cstate]) {
createLocationStateInDB(loc_prefix + curlocation['id'] + '.' + cstate + '.' + citem, citem, curlocation[cstate][citem]);
}
} else {
// simple state
createLocationStateInDB(loc_prefix + curlocation['id'] + '.' + cstate, cstate, curlocation[cstate]);
}
}
}
});
});
}
}
if (callback) callback(false);
};
// create a state for a datapoint in the DB
function createDatapointsStateInDB(id, state, cloud_data, callback) {
let obj = {
"_id": id,
"type": "state",
"common": {
"name": state.name,
"read": true,
"write": false,
"type": state.type,
"role": state.role
},
"native": {}
};
for(let i=0;i<id.split('.').slice(5, -1).length;i++) {
let cid = id.split('.').slice(5, -1)[i];
let grp = {
"_id": id.split('.').slice(0, 5 + i + 1).join('.'),
"type": "",
"common": {},
"native": {}
};
// can we find the id?
let res = jsonPath.query(cloud_data, '$..[?(@.id=="' + cid + '")]');
if(res && Array.isArray(res) && res.length === 0) {
grp.common['name'] = cid;
} else {
grp.common['name'] = res[0].name;
}
adapter.setObjectNotExists(grp._id, grp, function(err) {
if(callback) callback(err);
});
}
adapter.setObjectNotExists(id, obj, function(err) {
if(callback) callback(err);
});
}
// create a smart datapoint in the DB
function createSmartDatapointsStateInDB(curid, state, cloud_data, callback) {
let id = curid.split('.')[curid.split('.').length-1];
let stateid = curid.replace('.' + id, '') + '.' + id;
// go through the id list
for(let i=0;i<stateid.split('.').slice(5).length;i++) {
let cid = stateid.split('.').slice(5)[i];
// can we find the id?
let res = jsonPath.query(cloud_data, '$..[?(@.id=="' + cid + '")]');
let grp = {
"_id": stateid.split('.').slice(0, 5 + i + 1).join('.'),
"type": "",
"common": {},
"native": {}
};
if(res && Array.isArray(res) && res.length === 0) {
grp.common['name'] = cid;
} else {
grp.common['name'] = res[0].name;
}
adapter.setObjectNotExists(stateid.split('.').slice(0, 5 + i + 1).join('.'), grp, function(err) {
if (callback) callback(err);
});
}
// query the last data from the cloud to get the names for the groups
// complex datapoint?
if(state.hasOwnProperty('unit') && state.unit === 'complex' && state.hasOwnProperty('value') && typeof(state.value) === 'object') {
// oh a complex value setup a sub group called properties
let sub_obj = {
"_id": stateid + '.smart_trigger',
"type": "state",
"common": {
"name": state.name,
"desc": "Send command with properties",
"role": "switch",
"type": "boolean",
"read": true,
"write": true,
"def": false
},
"native": {}
};
adapter.setObjectNotExists(stateid + '.smart_trigger', sub_obj, function(err) {
if (callback) callback(err);
});
let sub_grp = {
"_id": stateid + '.properties',
"type": "",
"common": {
"name": 'properties'
},
"native": {}
};
adapter.setObjectNotExists(stateid + '.properties', sub_grp, function(err) {
if(err) {
if(callback) callback(err);
} else {
// now we can create states for the properties
for(let celem in state.value) {
let type = typeof(state.value[celem]);
let role = 'state';
switch(type) {
case 'string':
role = 'text';
break;
case 'number':
role = 'level';
break;
case 'boolean':
role = 'switch';
break;
}
let sub_sub_obj = {
"_id": stateid + '.properties.' + celem,
"type": "state",
"common": {
"name": celem,
"desc": celem,
"read": true,
"type": type,
"role": role,
"write": true
},
"native": {}
};
adapter.setObjectNotExists(stateid + '.properties.' + celem, sub_sub_obj, function(err) {
if (err) {
if (callback) callback(err);
}
});
}
}
});
} else {
// no complex value with properties
// get some more attributes
let res = jsonPath.query(cloud_data, '$..[?(@.id=="' + stateid.split('.').slice(-1) + '")]');
let obj = {
"_id": stateid,
"type": "state",
"native": {}
};
let common = {
"name": state.name,
"desc": "Smart Datapoint",
"read": true,
};
if (state.hasOwnProperty('writeable')) {
common["write"] = state.writeable;
}
if (state.hasOwnProperty('type')) {
common["type"] = state.type;
} else {
// try to get the type from the value
if(res && Array.isArray(res) && res.length > 0 && res[0].hasOwnProperty('value') && res[0].value) {
common['type'] = typeof(res[0].value)
}
}
if (state.hasOwnProperty('unit')) {
common["unit"] = state.unit;
}
if (state.hasOwnProperty('role')) {
common['role'] = state.role;
}
if((common.hasOwnProperty('role') && common['role'] === 'state') || !common.hasOwnProperty('role')) {
// check if role is simply state
// if it is so try a better guess
if (common.hasOwnProperty('type') && common.type === 'string') {
common['role'] = 'text';
} else if (common.hasOwnProperty('type') && common.type === 'boolean' && common.hasOwnProperty('write') && common.write === true && common.hasOwnProperty('read') && common.read === false) {
common['role'] = 'button';
} else if(common.hasOwnProperty('type') && common.type === 'number' && common.hasOwnProperty('write') && common.write === false) {
common['role'] = 'value';
} else if (common.hasOwnProperty('type') && common.type === 'boolean' && common.hasOwnProperty('write') && common.write === false) {
common['role'] = 'sensor';
} else if (common.hasOwnProperty('type') && common.type === 'number' && common.hasOwnProperty('write') && common.write === true) {
common['role'] = 'level';
} else if (common.hasOwnProperty('type') && common.type === 'boolean' && common.hasOwnProperty('write') && common.write === true) {
common['role'] = 'switch';
}
}
if (res && Array.isArray(res) && res.length > 0 && res[0].hasOwnProperty('supported_values') && Array.isArray(res[0].supported_values) && res[0].supported_values.length > 0) {
// we have some kind of enum here
let enum_states = {};
for(let i=0;i<res[0].supported_values.length;i++) {
enum_states[i] = res[0].supported_values[i];
}
common["states"] = enum_states;
}
obj['common'] = common;
adapter.setObjectNotExists(stateid, obj, function (err) {
if (callback) callback(err);
});
}
}
// create devices states under datapoints
function createLocationStatesInDBDatapoints(locations, names, callback) {
let states_todo = locations.length;
let states_done = 0;
if(!locations || locations.length === 0) callback(false);
for(let i=0;i<locations.length;i++) {
let obj = {
"_id": dp_prefix + locations[i],
"type": "",
"common": {
"name": names[i]
},
"native": {}
};
adapter.setObjectNotExists(dp_prefix + locations[i], obj, function(err) {
states_done += 1;
if(states_done === states_todo) {
callback(false);
}
});
}
}
// create devices states under datapoints.[location].device
function createDeviceStatesInDBDatapoints(devices, names, callback) {
let states_todo = devices.length;
let states_done = 0;
if(!devices || devices.length === 0) callback(false);
for(let i=0;i<devices.length;i++) {
let obj = {
"_id": dp_prefix + devices[i],
"type": "device",
"common": {
"name": names[i]
},
"native": {}
};
adapter.setObjectNotExists(dp_prefix + devices[i], obj, function(err) {
states_done += 1;
if(states_done === states_todo) {
callback(false);
}
});
}
}
// this function is used for syncing datapoints in the DB with the ones from the config
exports.syncDBDatapoints = function (cloud_data, callback) {
let config_states = adapter.config.gardena_datapoints;
let smart_datapoints = adapter.config.gardena_smart_datapoints;
let locations = [];
let names = [];
for(let cstate in config_states) {
if(!locations.includes(cstate.split(conf_seperator)[0])) {
locations.push(cstate.split(conf_seperator)[0]);
names.push(config_states[cstate].location);
}
}
// collect distinct locations
createLocationStatesInDBDatapoints(locations, names, function(err) {
// collect distinct device states
let devices = [];
names = [];
for(let cstate in config_states) {
if(!devices.includes(cstate.split(conf_seperator)[0] + '.' + cstate.split(conf_seperator)[1])) {
devices.push(cstate.split(conf_seperator)[0] + '.' + cstate.split(conf_seperator)[1]);
names.push(config_states[cstate].device.name);
}
}
// create device states in DB
createDeviceStatesInDBDatapoints(devices, names, function(err) {
if(err) {
adapter.log.error(err);
if(callback) callback(err);
return
}
// after creation, get all states already present in the db
adapter.getStates('gardena.' + adapter.instance + '.datapoints.*', function (err, states) {
// add datapoints if needed
for(let cstate in config_states) {
// check if we have a smart datapoint here
if(config_states[cstate].hasOwnProperty('smart') && config_states[cstate].smart && smart_datapoints) {
let smart_in_db = false;
// check if the datapoint is already there
for(let cstateindb in states) {
if(cstateindb.includes(cstate.replace(conf_re_seperator, '.'))) {
smart_in_db = true;
break;
}
}
if(!smart_in_db) {
// create SMART datapoint
createSmartDatapointsStateInDB(dp_prefix + cstate.replace(conf_re_seperator, '.'), config_states[cstate], cloud_data);
}
} else {
// simply check if the dp is there
if(!states.hasOwnProperty(dp_prefix + cstate.replace(conf_re_seperator, '.'))) {
// state has to be created
createDatapointsStateInDB(dp_prefix + cstate.replace(conf_re_seperator, '.'), config_states[cstate], cloud_data);
}
}
}
// remove datapoints if they are not in the config
for(let cdp in states) {
// check if the state belongs to a smart one
let found = false;
for(let cconf in config_states) {
if(cdp.includes(cconf.replace(conf_re_seperator, '.'))) {
found = true;
break;
}
}
if(!found) {
// datapoint has to be removed
let cdpex = cdp.replace(dp_prefix, '').replace(/\./g, conf_seperator);
adapter.delObject(dp_prefix + cdpex.replace(conf_re_seperator, '.'));
}
}
if(callback) callback(false);
});
});
});
};
// updates a single datapoint by traversing through the devices structure
function updateSingleDBDatapoint(curid, locationid, devices) {
// setup a query
if(curid.split(conf_seperator).length <= 1) {
adapter.log.error('There is invalid id ' + curid + ' in the config!');
return;
}
let prop = curid.split(conf_seperator)[curid.split(conf_seperator).length-1];
let id = curid.split(conf_seperator)[curid.split(conf_seperator).length-2];
let data = {"data": {"devices": devices}};
let query = '$..[?(@.id=="' + id + '")]';
let res = jsonPath.query(data, query);
// try to find key in devices structure
if(!res || !Array.isArray(res) || !res.length > 0 || !res[0].hasOwnProperty(prop)) {
// we have a small datapoint
let sub_query = '$..[?(@.id=="' + prop + '")]';
let sub_res = jsonPath.query(data, sub_query);
if(sub_res && Array.isArray(sub_res) && sub_res.length > 0 && sub_res[0].hasOwnProperty('value') && typeof(sub_res[0].value) === 'object') {
// update properties of smart datapoint
for(let celem in sub_res[0].value) {
adapter.setState(dp_prefix + locationid + '.' + curid.replace(conf_re_seperator, '.') + '.properties.' + celem, sub_res[0].value[celem]);
}
} else if(sub_res && Array.isArray(sub_res) && sub_res.length > 0 && sub_res[0].hasOwnProperty('value')) {
adapter.setState(dp_prefix + locationid + '.' + curid.replace(conf_re_seperator, '.'), sub_res[0].value);
} else {
adapter.log.warn('Warning, state ' + curid + ' is present in the database but missing in the datagram from Gardena cloud.');
}
} else {
// fine, set the value
adapter.setState(dp_prefix + locationid + '.' + curid.replace(conf_re_seperator, '.'), res[0][prop]);
}
}
// update datapoints after polling
exports.updateDBDatapoints = function (locationid, devices, callback) {
let settings_dp = adapter.config.gardena_datapoints;
// go through all states from the settings and update their values
for(let cdp in settings_dp) {
let cdpx = cdp.slice(cdp.indexOf(conf_seperator) + conf_seperator.length);
updateSingleDBDatapoint(cdpx, locationid, devices.devices);
}
if (callback) callback(false);
};
// create set commands for devices that support only post commands (these commands have to be supplied by gardena_commands.json)
exports.createHTTPPostDatapointsinDB = function(cloud_data, callback) {
// get all categories in the gardena commands file
let cat_commands = jsonPath.query(gardena_commands, '$..[?(@.category)]');
// first get all devices in the DB and check their category
adapter.getDevices(function(err, devices) {
// get category of device
for(let i=0;i<devices.length;i++) {
let cdev_id = devices[i]._id.split('.').slice(-1)[0]; // get id of the device
let device_cat = jsonPath.query(cloud_data, '$..datapoints..devices[?(@.id=="' + cdev_id + '")]');
if(!device_cat || !Array.isArray(device_cat) || !device_cat.length > 0 || !device_cat[0].hasOwnProperty('category')) {
adapter.log.debug('Could not find category for device ' + cdev_id);
continue;
}
// is this category present in the gardena commands file?
let in_commands = jsonPath.query(cat_commands, '$..[?(@.category=="' + device_cat[0].category + '")]');
if(in_commands.length > 0) {
// category present in file create the states
createCommandStatesInDB(devices[i], device_cat[0].category);
}
}
});
};
function createCommandStatesInDB(device, category, callback) {
// get commands
let cat_commands = jsonPath.query(gardena_commands, '$[?(@.category=="' + category + '")]')[0];
let baseURI = cat_commands.baseURI;
// setup object structure
for(let i=0;i<baseURI.split('/').length;i++) {
let grp = {
"_id": device._id + '.' + baseURI.split('/').slice(0, i + 1).join('.'),
"type": "",
"common": {
"name": baseURI.split('/')[i]
},
"native": {}
};
adapter.setObjectNotExists(grp._id, grp, function(err) {
if(callback) callback(err);
});
}
// setup the commands
let commands_to_create = cat_commands.commands;
for(let i=0;i<commands_to_create.length;i++) {
// create subgroup for command
let grp = {
"_id": device._id + '.' + baseURI.split('/').join('.') + '.' + commands_to_create[i].name,
"type": "",
"common": {
"name": commands_to_create[i].cmd_desc
},
"native": {}
};
adapter.setObjectNotExists(grp._id, grp, function(err) {
if(!err) {
// create trigger state
let obj = {
"_id": device._id + '.' + baseURI.split('/').join('.') + '.' + commands_to_create[i].name + '.' + 'trigger',
"type": "state",
"common": {
"name": commands_to_create[i].name,
"read": true,
"write": true,
"role": "switch",
"type": "boolean",
"def": false
},
"native": {}
};
adapter.setObjectNotExists(obj._id, obj, function(err) {});
// are there parameters?
if(commands_to_create[i].hasOwnProperty('parameters') && commands_to_create[i].parameters.length > 0 ) {
// create parameter group
let grp = {
"_id": device._id + '.' + baseURI.split('/').join('.') + '.' + commands_to_create[i].name + '.parameters',
"type": "",
"common": {
"name": "parameters"
},
"native": {}
};
adapter.setObjectNotExists(grp._id, grp, function(err) {
// now create the parameters
for(let j=0;j<commands_to_create[i].parameters.length;j++) {
let cparam = commands_to_create[i].parameters[j];
let desc = 'parameter for ' + commands_to_create[i].name;
if(cparam.hasOwnProperty('desc')) desc = cparam.desc;
let role = 'state';
if(cparam.hasOwnProperty('role')) role = cparam.role;
let write = true;
if(cparam.hasOwnProperty('write')) write = cparam.write;
let val;
if(cparam.hasOwnProperty('val')) val = cparam.val;
let obj = {
"_id": device._id + '.' + baseURI.split('/').join('.') + '.' + commands_to_create[i].name + '.parameters.' + cparam.name,
"type": "state",
"common": {
"name": cparam.name,
"desc": desc,
"read": true,
"write": write,
"role": role,
"type": cparam.type,
"def": val
},
"native": {}
};
adapter.setObjectNotExists(obj._id, obj, function(err) {
// set the value
if(!err && val) adapter.setState(obj._id, val)
});
}
});
}
}
if(callback) callback(err);
});
}
if(callback) callback(false);
}