@bertreb https://jsfiddle.net/7qLf8y1m/ there is just my Main.js
-
Customize - ioBroker.pimatic
-
Done
function syncVariables(variables, callback) { var objs = []; var _states = []; for (var v = 0; v < variables.length; v++) { var localObjects = []; var variable = variables[v]; if (variable.readonly == false) { adapter.log.debug('Handle Variables: ' + JSON.stringify(variable)); var obj = { _id: adapter.namespace + '.Variables.' + variable.name, common: { name: variable.name, read: true, write: true, }, type: 'state' }; _states.push({ _id: adapter.namespace + '.Variables.' + variable.name, val: { ack: true, val: variable.value, } }); objs.push(obj); } } var ids = []; for (var j = 0; j < objs.length; j++) { ids.push(objs[j]._id); objects[objs[j]._id] = objs[j]; } syncObjects(objs, function () { syncStates(_states, function () { callback && callback(ids); }); }); }
Now i have the Variables and the values but the sync seems not to work in the moment
And i dont know what is this for:
var ids = []; for (var j = 0; j < objs.length; j++) { ids.push(objs[j]._id); objects[objs[j]._id] = objs[j]; }
-
i tired just changing “j” to “vJ” because its like the other “for”-loops. In the moment the Adapater crashes when i change a value. Because “control” is not defined.
host.leetwerkiobroker 2020-03-04 20:21:03.138 info Restart adapter system.adapter.pimatic.1 because enabled host.leetwerkiobroker 2020-03-04 20:21:03.138 info instance system.adapter.pimatic.1 terminated with code 0 (NO_ERROR) host.leetwerkiobroker 2020-03-04 20:21:03.138 error Caught by controller[0]: at processImmediate (timers.js:658:5) host.leetwerkiobroker 2020-03-04 20:21:03.137 error Caught by controller[0]: at tryOnImmediate (timers.js:676:5) host.leetwerkiobroker 2020-03-04 20:21:03.137 error Caught by controller[0]: at runCallback (timers.js:705:18) host.leetwerkiobroker 2020-03-04 20:21:03.137 error Caught by controller[0]: at Immediate.setImmediate (/opt/iobroker/node_modules/iobroker.js-controller/lib/adapter.js:4852:34) host.leetwerkiobroker 2020-03-04 20:21:03.137 error Caught by controller[0]: at Adapter.emit (events.js:198:13) host.leetwerkiobroker 2020-03-04 20:21:03.137 error Caught by controller[0]: at Adapter.<anonymous> (/opt/iobroker/node_modules/iobroker.pimatic/main.js:42:64) host.leetwerkiobroker 2020-03-04 20:21:03.137 error Caught by controller[0]: TypeError: Cannot read property 'control' of undefined pimatic.1 2020-03-04 20:21:02.618 info (3890) Terminated (NO_ERROR): Without reason pimatic.1 2020-03-04 20:21:02.618 info (3890) terminating pimatic.1 2020-03-04 20:21:02.612 info (3890) cleaned everything up... pimatic.1 2020-03-04 20:21:02.612 info (3890) disconnected
-
@bertreb said in Customize - ioBroker.pimatic:
you need to change j to vJ 6 times!
yep i know … but i think is now a problem maybe of the “sync”-function or of this “Iobroker” stuff, there some other Attributes that there give to the devices:
For example:
obj.common.role = ‘indicator.battery’;
obj.native.mapping = {‘ok’: false, ‘low’: true};
obj.common.type = ‘boolean’;
obj.common.states = {false: ‘ok’, true: ‘low’};native: { name: p, control: { action: action.name, deviceId: device.id }
maybe i miss some of them… i found this https://github.com/ioBroker/ioBroker/blob/master/doc/SCHEMA.md maybe this will help to understand
-
Okay … Progress
function syncVariables(variables, callback) { var objs = []; var _states = []; for (var v = 0; v < variables.length; v++) { var localObjects = []; var variable = variables[v]; if (variable.readonly == false) { adapter.log.debug('Handle Variables: ' + JSON.stringify(variable)); var obj = { _id: adapter.namespace + '.Variables.' + variable.name, common: { name: variable.name, read: true, write: true, }, native: { control: { action: 'set ' + variable.name + ' to', deviceId: variable.name } }, type: 'state' }; _states.push({ _id: adapter.namespace + '.Variables.' + variable.name, val: { ack: true, val: variable.value, } }); objs.push(obj); } } var ids = []; for (var vj = 0; vj < objs.length; vj++) { ids.push(objs[vj]._id); objects[objs[vj]._id] = objs[vj]; } syncObjects(objs, function () { syncStates(_states, function () { callback && callback(ids); }); }); }
now it will not crash but gives out this Error:
pimatic.1 2020-03-04 20:45:44.305 warn (3995) Cannot write "pimatic.1.Variables.movement_time_bad": {"success":false,"message":"device not found: movement_time_bad"}
And this is the firsttime pretty logic because its the response from Pimatic RESTAPI … and of cause he cant find it as a device because its a variable
-
I thing i reached the end of the line, last thing is to change the variable that should over “adapter.on(‘stateChange’”,
in this function there build URLs like this and send them via Websocket:pimatic.1 2020-03-04 21:55:21.766 info (4228) http://USER:PASS@IP:PORT/api/device/lichterkette/changeStateTo?state=true pimatic.1 2020-03-04 21:54:02.173 info (4228) http://USER:PASS@IP:PORT/api/device/lichterkette/changeStateTo?state=false
Now i think i need a “IF-block” to change it if its a variable… but “variables” is Empty in this function. So cant check if the “Object” is in the Array
And it seems that it will not work with “…/api/variables/updateVariable…”. I found this “updateVariable” in the Documentation of the Pimatic Websocket but i dont understand it.
"You can also call actions from the socket.io connection:
socket.emit('call', { id: 'update-variable-call1', action: 'updateVariable', params: { name: 'the-answer', type: 'value', valueOrExpression: 1337 } }); socket.on('callResult', function(msg){ if(msg.id === 'update-variable-call1') { console.log(msg.result); } });
" https://pimatic.org/guide/api/
Anybody a idea for the last Step?
-
You can use a name that is <device-name>.<attribute-name> or only <variable-name> for standalone variables
Something like this (i didn’t try this yet)
socket.emit('call', { id: 'update-variable-call1', action: 'updateVariable', params: { name: 'lichterkette.state', type: 'value', valueOrExpression: true } }); socket.on('callResult', function(msg){ if(msg.id === 'update-variable-call1') { console.log(msg.result); } });
-
@bertreb said in Customize - ioBroker.pimatic:
You can use a name that is <device-name>.<attribute-name> or only <variable-name> for standalone variables
sorry i dont understand this you explain it with this strange Websocket URLs?
-
For identifying a variable the id(or name) is with or with or without a dot.
With a dot is a variable thats is part of a device (its called an attribute)
Without a dot its a standalone variable you can create via the gui -
@bertreb said in Customize - ioBroker.pimatic:
You can use a name that is <device-name>.<attribute-name> or only <variable-name> for standalone variables
Something like this (i didn’t try this yet)
socket.emit('call', { id: 'update-variable-call1', action: 'updateVariable', params: { name: 'lichterkette.state', type: 'value', valueOrExpression: true } }); socket.on('callResult', function(msg){ if(msg.id === 'update-variable-call1') { console.log(msg.result); } });
so i have to build if block with is the “Action” = “updateVariable” then and this socket.emit stuff? Because i push now every of my “variables” to Iobroker with this “native Action” “Updatevariable” so i have this as identifier.
-
I’m not sure i understand your question.
The above script is for updating a Pimatic variable. The params.name must come from the ioBroker variables initiatization (syncVariables).
When in ioBroker a variable is updated this function should be called (if the variable is read/write of course) -
Anyway Its working as I expected. Updating works now to Im my last test i find out that if i change it in pimatic it will not automatic synct back … so when got this also i will post the whole code for the Main.js here and i my github issue … so somebody can found it when there need this
Here the Part of the “adapter.on(‘stateChange’…”
...... // Update Variables mod by themilcho if (objects[id].native.control.action == 'updateVariable') { client.emit('call', { id: 'update-variable-call1', action: 'updateVariable', params: { name: objects[id].native.control.deviceId, type: 'value', valueOrExpression: state.val } }); } else { var link = getUrl + 'api/device/' + objects[id].native.control.deviceId + '/' + objects[id].native.control.action + '?' + objects[id].native.name + '=' + state.val; adapter.log.debug('http://' + link); request('http://' + credentials + link, function (err, res, body) { if (err || res.statusCode !== 200) { .........
-
@bertreb said in Customize - ioBroker.pimatic:
Nico, to prevent a message loop when you receive an update, you can test if the value is the same as the actual value and then do nothing.
This is something for a another Day … like a cherry on a cake… now i will be pretty happy it is just works as i want
-
Sooo okay … i cant figure out how i get Updates for the Varaibles (Change in Pimatic -> Iobroker) … so i give up this point…
If some one find a solution tell meHere is my final Version of my Mod, i changed now the placement of the variables back to the Iobroker “Devices” because its loot easy and i get rid of ~ 80 Warn-Entrys in the Iobroker-log. The Variables are marked with the Role “pimatic-variable”, so you can find them easy or filter for them. The push from Iobroker to Pimatic of the variables works fine. if you need also the Updates of the variables, create a “variablesdevices” in pimatic with the varaibles this the devices will be synct properly but the variables are readonly in the device.
Installation:
- if you had only ready used the Adapter: stop the instance and delete the folder “devices” unter “Pimatic.0”
- replace the “main.js” under “opt/iobroker/node_modules/iobroker.pimatic” and start the Instance
Code for main.js:
/** * * pimatic adapter Copyright 2017, bluefox <dogafox@gmail.com> * */ /* jshint -W097 */ /* jshint strict:false */ /* jslint node: true */ 'use strict'; // you have to require the utils module and call adapter function var utils = require('@iobroker/adapter-core'); // Get common adapter utils var request = require('request'); var adapter = utils.Adapter('pimatic'); var io = require('socket.io-client'); var client; var objects = {}; var states = []; var connected = false; var url; var getUrl; var credentials; // is called when adapter shuts down - callback has to be called under any circumstances! adapter.on('unload', function (callback) { try { if (adapter.setState) adapter.setState('info.connection', false, true); if (client) client.disconnect(); client = null; adapter.log.info('cleaned everything up...'); callback(); } catch (e) { callback(); } }); // is called if a subscribed state changes adapter.on('stateChange', function (id, state) { if (state && !state.ack) { if (objects[id]) { if (objects[id].common.write && objects[id].native.control && objects[id].native.control.action) { states[id] = state.val; if (!connected) { adapter.log.warn('Cannot control: no connection to pimatic "' + adapter.config.host + '"'); } else { /*client.emit('call', { id: id, action: objects[id].native.control.action, params: { deviceId: objects[id].native.control.deviceId, name: objects[id].native.name, type: objects[id].common.type, valueOrExpression: state.val } });*/ // convert values if (objects[id].common.type === 'boolean') { state.val = (state.val === true || state.val === 'true' || state.val === '1' || state.val === 1 || state.val === 'on' || state.val === 'ON'); } else if (objects[id].common.type === 'number') { if (typeof state.val !== 'number') { if (state.val === true || state.val === 'true' || state.val === 'on' || state.val === 'ON') { state.val = 1; } else if (state.val === false || state.val === 'false' || state.val === 'off' || state.val === 'OFF') { state.val = 0; } else { state.val = parseFloat((state.val || '0').toString().replace(',', '.')); } } } // Update Variables mod by themilcho if (objects[id].native.control.action == 'updateVariable') { client.emit('call', { // id: id, id: objects[id].native.control.deviceId, action: 'updateVariable', params: { name: objects[id].native.control.deviceId, type: 'value', valueOrExpression: state.val } }); adapter.setForeignState(id, {val: state.val, ack: true}); } else { var link = getUrl + 'api/device/' + objects[id].native.control.deviceId + '/' + objects[id].native.control.action + '?' + objects[id].native.name + '=' + state.val; adapter.log.debug('http://' + link); request('http://' + credentials + link, function (err, res, body) { if (err || res.statusCode !== 200) { adapter.log.warn('Cannot write "' + id + '": ' + (body || err || res.statusCode)); adapter.setForeignState(id, {val: state.val, ack: true, q: 0x40}); } else { try { var data = JSON.parse(body); if (data.success) { adapter.log.debug(body); // the value will be updated in deviceAttributeChanged } else { adapter.log.warn('Cannot write "' + id + '": ' + body); adapter.setForeignState(id, {val: state.val, ack: true, q: 0x40}); } } catch (e) { adapter.log.warn('Cannot write "' + id + '": ' + body); adapter.setForeignState(id, {val: state.val, ack: true, q: 0x40}); } } }); } } } else { adapter.log.warn('State "' + id + '" is read only'); } } else { adapter.log.warn('Unknown state "' + id + '"'); } } }); // Some message was sent to adapter instance over message box. Used by email, pushover, text2speech, ... adapter.on('message', function (obj) { if (typeof obj === 'object' && obj.message) { if (obj.command === 'send') { // e.g. send email or pushover or whatever console.log('send command'); // Send response in callback if required if (obj.callback) adapter.sendTo(obj.from, obj.command, 'Message received', obj.callback); } } }); // is called when databases are connected and adapter received configuration. // start here! adapter.on('ready', function () { main(); }); function syncObjects(objs, callback) { if (!objs || !objs.length) { callback && callback(); return; } var obj = objs.shift(); adapter.getForeignObject(obj._id, function (err, oObj) { if (!oObj) { objects[obj._id] = obj; adapter.setForeignObject(obj._id, obj, function () { setTimeout(syncObjects, 0, objs, callback); }); } else { var changed = false; for (var a in obj.common) { if (obj.common.hasOwnProperty(a) && oObj.common[a] !== obj.common[a]) { changed = true; oObj.common[a] = obj.common[a]; } } if (JSON.stringify(obj.native) !== JSON.stringify(oObj.native)) { changed = true; oObj.native = obj.native; } objects[obj._id] = oObj; if (changed) { adapter.setForeignObject(oObj._id, oObj, function () { setTimeout(syncObjects, 0, objs, callback); }); } else { setTimeout(syncObjects, 0, objs, callback); } } }); } function syncStates(_states, callback) { if (!_states || !_states.length) { callback && callback(); return; } var state = _states.shift(); adapter.getForeignState(state._id, function (err, oState) { if (!oState) { adapter.setForeignState(state._id, state.val, function () { setTimeout(syncStates, 0, _states, callback); }); } else { var changed = false; for (var a in state.val) { if (state.val.hasOwnProperty(a) && (typeof state.val[a] !== 'object' && state.val[a] !== oState[a]) || (typeof state.val[a] === 'object' && JSON.stringify(state.val[a]) !== JSON.stringify(oState[a]))) { changed = true; oState[a] = state.val[a]; } } if (changed) { adapter.setForeignState(oState._id, oState, function () { setTimeout(syncStates, 0, _states, callback); }); } else { setTimeout(syncStates, 0, _states, callback); } } }); } function syncDevices(devices, callback) { var objs = []; var _states = []; for (var d = 0; d < devices.length; d++) { var localObjects = []; var device = devices[d]; // adapter.log.debug('Handle Device: ' + JSON.stringify(device)); adapter.log.debug('Handle Device: ' + device.id); var obj = { _id: adapter.namespace + '.devices.' + device.id, common: { name: device.name }, type: 'channel' }; objs.push(obj); var attributes = device.attributes; if ((!attributes || !attributes.length) && device.config) attributes = device.config.attributes; if (attributes && attributes.length) { for (var a = 0; a < attributes.length; a++) { var attr = attributes[a]; adapter.log.debug('Handle Attribute: ' + JSON.stringify(attr)); var id = adapter.namespace + '.devices.' + device.id + '.' + attr.name.replace(/\s/g, '_'); obj = { _id: id, common: { name: device.name + ' - ' + (attr.acronym || attr.name), desc: attr.description, type: attr.type, read: true, write: false, unit: attr.unit === 'c' ? '°C' : (attr.unit === 'f' ? '°F' : attr.unit) //role: acronym2role(attr.acronym) }, native: { }, type: 'state' }; _states.push({ _id: id, val: { ack: true, val: attr.value, ts: attr.lastUpdate } }); states[id] = attr.value; delete attr.value; delete attr.lastUpdate; delete attr.history; obj.native = attr; if (obj.common.type === 'boolean') { if (device.template === 'presence') obj.common.role = 'state';//'indicator.presence'; if (attr.labels && attr.labels[0] !== 'true') { obj.common.states = {false: attr.labels[1], true: attr.labels[0]}; } } else if (obj.common.type === 'number') { if (obj.common.unit === '°C' || obj.common.unit === '°F') { obj.common.role = 'value.temperature'; } else if (obj.common.unit === '%') { obj.common.min = 0; obj.common.max = 100; // Detect if temperature exists var found = false; for (var k = 0; k < localObjects.length; k++) { if (localObjects[k].common.unit === '°C' || localObjects[k].common.unit === '°F') { found = true; break; } } if (found) { obj.common.role = 'value.humidity'; } } if (attr.name === 'latitude') { obj.common.role = 'value.gps.latitude'; } else if (attr.name === 'longitude') { obj.common.role = 'value.gps.longitude'; } if (attr.name === 'gps') { obj.common.role = 'value.gps'; } } else { if (attr.name === 'battery') { obj.common.role = 'indicator.battery'; obj.native.mapping = {'ok': false, 'low': true}; obj.common.type = 'boolean'; obj.common.states = {false: 'ok', true: 'low'}; attr.value = (attr.value !== 'ok'); } } if (attr.enum && !obj.common.states) { obj.common.states = {}; for (var e = 0; e < attr.enum.length; e++) { if (attr.enum[e] === 'manu') { obj.common.states.manu = 'manual'; } else if (attr.enum[e] === 'auto') { obj.common.states.auto = 'automatic'; } else{ obj.common.states[attr.enum[e]] = attr.enum[e]; } } } objs.push(obj); localObjects.push(obj); } } var actions = device.actions; if ((!actions || !actions.length) && device.config) actions = device.config.actions; if (actions && actions.length) { for (var c = 0; c < actions.length; c++) { var action = actions[c]; for (var p in action.params) { if (!action.params.hasOwnProperty(p)) continue; // try to find state for that var _found = false; for (var u = 0; u < localObjects.length; u++) { if (localObjects[u].native.name === p) { _found = true; obj = localObjects[u]; obj.native.control = { action: action.name, deviceId: device.id }; obj.common.write = true; if (obj.common.role === 'value.temperature') obj.common.role = 'level.temperature'; } } if (!_found) { obj = { _id: adapter.namespace + '.devices.' + device.id + '.' + action.name.replace(/\s/g, '_') + '.' + p.replace(/\s/g, '_'), common: { desc: action.params[p].description || action.description, name: device.name + ' - ' + action.name + '.' + p, read: false, write: true, type: action.params[p].type }, native: { name: p, control: { action: action.name, deviceId: device.id } }, type: 'state' }; objs.push(obj); } } } } } var ids = []; for (var j = 0; j < objs.length; j++) { ids.push(objs[j]._id); objects[objs[j]._id] = objs[j]; } syncObjects(objs, function () { syncStates(_states, function () { callback && callback(ids); }); }); } //Update variables mod by tehmilcho function syncVariables(variables, callback) { var objs = []; var _states = []; for (var v = 0; v < variables.length; v++) { var localObjects = []; var variable = variables[v]; if (variable.readonly == false) { adapter.log.debug('Handle Variables: ' + JSON.stringify(variable)); var obj = { _id: adapter.namespace + '.devices.' + variable.name, common: { name: variable.name, read: true, write: true, role: 'pimatic-variable' }, native: { name: variable.name, control: { action: 'updateVariable', deviceId: variable.name } }, type: 'state' }; _states.push({ _id: adapter.namespace + '.devices.' + variable.name, val: { ack: true, val: variable.value, } }); objs.push(obj); localObjects.push(obj); } } var ids = []; for (var vj = 0; vj < objs.length; vj++) { ids.push(objs[vj]._id); objects[objs[vj]._id] = objs[vj]; } syncObjects(objs, function () { syncStates(_states, function () { callback && callback(ids); }); }); } function syncGroups(groups, ids, callback) { var enums = []; var obj = { _id: 'enum.pimatic', common: { members: [], name: 'Pimatic groups' }, native: {}, type: 'enum' }; enums.push(obj); for (var g = 0; g < groups.length; g++) { obj = { _id: 'enum.pimatic.' + groups[g].id, type: 'enum', common: { name: groups[g].name, members: [] }, native: {} }; for (var m = 0; m < groups[g].devices.length; m++) { var id = adapter.namespace + '.devices.' + groups[g].devices[m].replace(/\s/g, '_'); if (ids.indexOf(id) === -1) { // try to find var found = false; var _id = id.toLowerCase(); for (var i = 0; i < ids.length; i++) { if (ids[i].toLowerCase() === _id) { id = ids[i]; found = true; break; } } if (found) { obj.common.members.push(id); } else { adapter.log.warn('Device "' + groups[g].devices[m] + '" was found in the group "' + groups[g].name + '", but not found in devices'); } } else { obj.common.members.push(id); } } enums.push(obj); } syncObjects(enums, callback); } function updateConnected(isConnected) { if (connected !== isConnected) { connected = isConnected; adapter.setState('info.connection', connected, true); adapter.log.info(isConnected ? 'connected' : 'disconnected'); } } function connect() { url = url || 'http://' + adapter.config.host + (adapter.config.port ? ':' + adapter.config.port : '') + '/?username=' + encodeURIComponent(adapter.config.username) + '&password='; credentials = credentials || encodeURIComponent(adapter.config.username) + ':' + encodeURIComponent(adapter.config.password); getUrl = getUrl || '@' + adapter.config.host + (adapter.config.port ? ':' + adapter.config.port : '') + '/'; adapter.log.debug('Connect: ' + url + 'xxx'); client = io.connect(url + encodeURIComponent(adapter.config.password), { reconnection: true, reconnectionDelay: 1000, reconnectionDelayMax: 3000, timeout: 20000, forceNew: true }); client.on('connect', function() { updateConnected(true); }); client.on('event', function (data) { adapter.log.debug(data); }); client.on('disconnect', function (data) { updateConnected(false); }); client.on('devices', function (devices) { updateConnected(true); syncDevices(devices); }); client.on('rules', function (rules) { //adapter.log.debug('Rules ' + JSON.stringify(rules)); }); client.on('variables', function (variables) { syncVariables(variables); var _states = []; for (var s = 0; s < variables.length; s++) { if (variables[s].value !== undefined && variables[s].value !== null) { var state = { _id: adapter.namespace + '.devices.' + variables[s].name.replace(/\s/g, '_'), val: { val: variables[s].value, ack: true } }; if (objects[state._id]) { if (objects[state._id].native && objects[state._id].native.mapping) { if (objects[state._id].native.mapping[variables[s].value] !== undefined) { state.val.val = objects[state._id].native.mapping[variables[s].value]; } } _states.push(state); } else { adapter.log.warn('Unknown state: ' + state._id); } } } syncStates(_states); }); client.on('pages', function (pages) { //adapter.log.debug('pages ' + JSON.stringify(pages)); }); client.on('groups', function (groups) { updateConnected(true); var ids = []; for (var id in objects) { ids.push(id); } syncGroups(groups, ids); }); client.on('deviceAttributeChanged', function (attrEvent) { if (!attrEvent.deviceId || !attrEvent.attributeName) { adapter.log.warn('Received invalid event: ' + JSON.stringify(attrEvent)); return; } var name = attrEvent.deviceId.replace(/\s/g, '_') + '.' + attrEvent.attributeName.replace(/\s/g, '_'); adapter.log.debug('update for "' + name + '": ' + JSON.stringify(attrEvent)); //{deviceId: device.id, attributeName, time: time.getTime(), value} var id = adapter.namespace + '.devices.' + name; if (objects[id]) { adapter.setForeignState(id, {val: attrEvent.value, ts: attrEvent.time, ack: true}); } else { adapter.log.warn('Received update for unknown state: '+ id + ' ' + JSON.stringify(attrEvent)); } }); client.on('callResult', function (msg) { if (objects[msg.id]) { adapter.setForeignState(msg.id, states[msg.id].val, true); } }); } function main() { adapter.setState('info.connection', false, true); connect(); // in this pimatic all states changes inside the adapters namespace are subscribed adapter.subscribeStates('*'); }