NEWS
IoBroker mit Warema WMS Web Control
-
Na so unfertig ist der doch gar nicht.
Ja die PANID ist im little-endian Format im MAC Frame und auch der WMS Stick erwartet ihn eigentlich immer im little-endian Format.
Ich hab bei den Analysen nur von der WMS Studio Software beim Scan die PANID big-endian bekommen, geh daher immer noch davon aus das das ein Fehler in der WMS Studio Software ist.
Ich würde daher im Decoder auch die PANID nicht umdrehen brauchen Sie ja eh little-endian.
Edit: Temperatur könnte noch little-endian sein. Dann werden positive Temperaturen allerdings als negative Zahlen geschickt. Lumen hab ich meine Vermutung zur Berechnung eingebaut, ist ganz sicher nicht little-endian.
Ansonsten hab ich deinen Decoder mal in meinen node-red Flow gehängt und etwas getestet.
Hab folgende Messages getestet:
-
G
-
V
-
50AC
-
7020
-
7021
Wollen wir hier bei den Zwischensteckern die noch unbekannte Payload mit ins Objekt packen?
-
7080
Hab hier mal meine aktuelle Lumen Berechnung eingebaut. Muss noch über die Grenzwerte prüfen ob das so hin kommt.
Könnte auch insgesamt nochmal mit 2 multipliziert werden müssen. Sollte aber schon näherungsweise hinkommen.
-
7071
-
8011
- 01000005
Positions request kommt manchmal auch mit der angefragten Parameter Nummer zurück.
-
0C000006
substr aufrufe korrigiert
-
8020
DateTime implementiert das unbekannte Feld zwischen minute und day of week sind die Sekunden. WebControl schickt es ziemlich genau einmal pro Minute…
Hier noch der Decoder mit meinen Änderungen:
function decodeWMS(packet) { packet = packet.replace(/^{|}$/g, ''); var obj = {}; switch (packet.substr(0, 1)) { case 'g': obj.type = 'stickType'; obj.payload = {name: packet.substr(1)}; break; case 'v': obj.type = 'stickVersion'; obj.payload = {version: packet.substr(1)}; break; case 'f': obj.type = 'error'; break; case 'a': obj.type = 'ack'; break; case 'r': obj.type = 'message'; obj.payload = decodeWMSMessage(packet.substr(1)); break; default: obj.type = 'unknown'; obj.payload = packet.substr(1); } return obj; } function decodeWMSMessage(message) { var obj = {}; obj.src = message.substr(0, 6); var type = message.substr(6, 4); var payload = message.substr(10); switch (type) { case '5018': obj.type = 'joinNetworkRequest'; obj.messagePayload = { panId: payload.substr(0, 4), networkKey: payload.substr(4, 32).match(/../g).reverse().join(""), unknown: payload.substr(36, 2), channel: parseInt(payload.substr(38, 2), 16) }; break; case '5060': obj.type = 'switchChannelRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), channel: parseInt(payload.substr(6, 2), 16) }; break; case '50AC': obj.type = 'ack'; obj.messagePayload = { unknown: payload.substr(0, 4) }; break; case '7020': obj.type = 'scanRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2) }; break; case '7021': obj.type = 'scanResponse'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2) //63: wetterstation, 06: webcontrol, 02: stick/software, 20: zwischenstecker }; break; case '7080': obj.type = 'weatherBroadcast'; obj.messagePayload = { unknown_1: payload.substr(0, 2), wind: parseInt(payload.substr(2, 2), 16), lumen_1: payload.substr(4, 2), unknown_2: payload.substr(6, 6), lumen_2: payload.substr(12, 2), unknown_3: payload.substr(14, 2), rain: payload.substr(16, 2) === 'C8', temp: parseInt(payload.substr(18, 2), 16) / 10, unknown_4: payload.substr(20), lumen: payload.substr(4, 2) === '00' ? parseInt(payload.substr(12, 2), 16) * 2 : parseInt(payload.substr(4, 2), 16) * parseInt(payload.substr(12, 2), 16) }; break; case '7050': obj.type = 'beckonRequest'; break; case '7070': obj.type = 'controlRequest'; obj.messagePayload = { unknown: payload.substr(0, 2), position: parseInt(payload.substr(2, 2), 16) / 2, angle: parseInt(payload.substr(4, 2), 16) - 127, valance_1: payload.substr(6, 2), valance_2: payload.substr(8, 2) }; break; case '7071': obj.type = 'controlResponse'; obj.messagePayload = payload; break; case '8010': obj.type = 'parameterGetRequest'; obj.messagePayload = { parameter: payload.substr(0) //01000005: position, 26000046: clock timer settings, 0C000006: auto modes & limits }; break; case '8011': obj.type = 'parameterGetResponse'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '01000003': //position case '01000005': //position obj.messagePayload.position = parseInt(payload.substr(8, 2), 16) / 2; obj.messagePayload.angle = parseInt(payload.substr(10, 2), 16) - 127; obj.messagePayload.valance_1 = payload.substr(12, 2); obj.messagePayload.valance_2 = payload.substr(14, 2); break; case '0C000006': //auto modes & limits obj.messagePayload.auto_wind = payload.substr(8, 2) !== '00' ? parseInt(payload.substr(8, 2), 16) + 4 : 0; //0 = off obj.messagePayload.auto_rain = payload.substr(10, 2) === '01'; obj.messagePayload.auto_sun = payload.substr(12, 2) !== '00' ? parseInt(payload.substr(12, 2), 16) * 5 + 5 : 0; //0 = off obj.messagePayload.auto_dusk = payload.substr(14, 2) !== '00' ? parseInt(payload.substr(14, 2), 16) : 0; //0 = off obj.messagePayload.auto_op = payload.substr(16, 2) === '01'; break; case '26000046': obj.messagePayload.unknown = payload.substr(8); break; default: obj.messagePayload.unknown = payload.substr(8); } break; case '8020': obj.type = 'parameterSetRequest'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '0B080009': obj.messagePayload.year = parseInt(payload.substr(8, 2), 16); obj.messagePayload.month = parseInt(payload.substr(10, 2), 16); obj.messagePayload.day = parseInt(payload.substr(12, 2), 16); obj.messagePayload.hour = parseInt(payload.substr(14, 2), 16); obj.messagePayload.minute = parseInt(payload.substr(16, 2), 16); obj.messagePayload.second = parseInt(payload.substr(18, 2), 16); obj.messagePayload.day_of_week = parseInt(payload.substr(20, 2), 16); obj.messagePayload.unknown_2 = payload.substr(22); break; default: obj.messagePayload.unknown = payload.substr(8); } //@todo break; default: obj.type = 'unknown'; obj.messagePayload = payload; } return obj; } msg.payload = decodeWMS(msg.payload); return msg;
-
-
7021
Wollen wir hier bei den Zwischensteckern die noch unbekannte Payload mit ins Objekt packen? `
Auf jeden Fall, ist mir wohl durch die Lappen gegangen. Alle anderen unknowns sind ja auch drin, dann kann man die bei neuen Erkenntnissen einfach ersetzen.lumen_1 und lumen_2 können auch rausfliegen, wenn du diese beide weiterverarbeiten konntest.
Ich werde morgen noch als Gegenstück einen Encoder basteln, damit man relativ einfach Packet zum senden erstellen kann.
-
Hier nochmal eine leicht angepasste Version des Decoders, mit deinen Änderungen.
Ich bin bei den Automatik-Modi zurück zu den Tatsächlichen Werten (0-9) gegangen. Ich denke damit kann man erstmal besser arbeiten. Wenn wir später eine Umrechnung (wie in den Tabellen von Warema) benötigen, dann machen wir das besser in einer anderen Funktion.
Anmerkung: die Funktion bezieht sich bei mir nur auf den Inhalt des Packets, ohne die Delimiter ({}). Ich nutze die Funktion im Moment zum testen mit https://www.npmjs.com/package/serialport und dort wird mir der Delimiter ohnehin abgeschnitten.
function decodeWMS(packet) { var obj = {}; switch (packet.substr(0, 1)) { case 'g': obj.type = 'stickType'; obj.payload = {name: packet.substr(1)}; break; case 'v': obj.type = 'stickVersion'; obj.payload = {version: packet.substr(1)}; break; case 'f': obj.type = 'error'; break; case 'a': obj.type = 'ack'; break; case 'r': obj.type = 'message'; obj.payload = decodeWMSMessage(packet.substr(1)); break; default: obj.type = 'unknown'; obj.payload = packet.substr(1); } return obj; } function decodeWMSMessage(message) { var obj = {}; obj.src = message.substr(0, 6); var type = message.substr(6, 4); var payload = message.substr(10); switch (type) { case '5018': obj.type = 'joinNetworkRequest'; obj.messagePayload = { panId: payload.substr(0, 4), networkKey: payload.substr(4, 32).match(/../g).reverse().join(""), unknown: payload.substr(36, 2), channel: parseInt(payload.substr(38, 2), 16) }; break; case '5060': obj.type = 'switchChannelRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), channel: parseInt(payload.substr(6, 2), 16) }; break; case '50AC': obj.type = 'ack'; obj.messagePayload = { unknown: payload.substr(0, 4) }; break; case '7020': obj.type = 'scanRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2) }; break; case '7021': obj.type = 'scanResponse'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), //63: wetterstation, 06: webcontrol, 02: stick/software, 20: zwischenstecker unknown: payload.substr(6) //optional }; break; case '7080': obj.type = 'weatherBroadcast'; obj.messagePayload = { unknown_1: payload.substr(0, 2), wind: parseInt(payload.substr(2, 2), 16), lumen: payload.substr(4, 2) === '00' ? parseInt(payload.substr(12, 2), 16) * 2 : parseInt(payload.substr(4, 2), 16) * parseInt(payload.substr(12, 2), 16), unknown_2: payload.substr(6, 6), unknown_3: payload.substr(14, 2), rain: payload.substr(16, 2) === 'C8', temp: parseInt(payload.substr(18, 2), 16) / 10, unknown_4: payload.substr(20) }; break; case '7050': obj.type = 'beckonRequest'; break; case '7070': obj.type = 'controlRequest'; obj.messagePayload = { unknown: payload.substr(0, 2), position: parseInt(payload.substr(2, 2), 16) / 2, angle: parseInt(payload.substr(4, 2), 16) - 127, valance_1: payload.substr(6, 2), valance_2: payload.substr(8, 2) }; break; case '7071': obj.type = 'controlResponse'; obj.messagePayload = payload; break; case '8010': obj.type = 'parameterGetRequest'; obj.messagePayload = { parameter: payload.substr(0) //01000005: position, 26000046: clock timer settings, 0C000006: auto modes & limits }; break; case '8011': obj.type = 'parameterGetResponse'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '01000003': //position case '01000005': //position obj.messagePayload.type = 'position'; obj.messagePayload.position = parseInt(payload.substr(8, 2), 16) / 2; obj.messagePayload.angle = parseInt(payload.substr(10, 2), 16) - 127; obj.messagePayload.valance_1 = payload.substr(12, 2); obj.messagePayload.valance_2 = payload.substr(14, 2); break; case '0C000006': //auto modes & limits obj.messagePayload.type = 'autoSettings'; obj.messagePayload.wind = parseInt(payload.substr(8, 2), 16); obj.messagePayload.rain = parseInt(payload.substr(10, 2), 16); obj.messagePayload.sun = parseInt(payload.substr(12, 2), 16); obj.messagePayload.dusk = parseInt(payload.substr(14, 2), 16); obj.messagePayload.op = parseInt(payload.substr(16, 2), 16); break; case '26000046': obj.messagePayload.type = 'clock'; obj.messagePayload.unknown = payload.substr(8); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; case '8020': obj.type = 'parameterSetRequest'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '0B080009': obj.messagePayload.type = 'clock'; obj.messagePayload.year = parseInt(payload.substr(8, 2), 16); obj.messagePayload.month = parseInt(payload.substr(10, 2), 16); obj.messagePayload.day = parseInt(payload.substr(12, 2), 16); obj.messagePayload.hour = parseInt(payload.substr(14, 2), 16); obj.messagePayload.minute = parseInt(payload.substr(16, 2), 16); obj.messagePayload.second = parseInt(payload.substr(18, 2), 16); obj.messagePayload.day_of_week = parseInt(payload.substr(20, 2), 16); obj.messagePayload.unknown = payload.substr(22); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; default: obj.type = 'unknown'; obj.messagePayload = payload; } return obj; }
-
Und hier der Encoder, allerdings kaum getestet.
Benutzung:
encodeWMS(type, parameter), wobei type z.B controlRequest und parameter dann '{dst: 'ABCDEF', position: 100, angle:0}'.
encodeWMS('controlRequest', {dst: 'ABCDEF', position: 100, angle:0});
Wenn type unbekannt ist, oder nicht alle nötigen Parameter angegeben sind, wird false zurückgegeben. Ansonsten wird der String zum Senden an den Stick zurückgegeben, ohne Delimiter ({}).
function encodeWMS(type, parameter) { if (!parameter) parameter = {}; switch (type) { case 'switchChannel': if (isNaN(parameter.channel)) return false; return '{M%' + parameter.channel + 'FFFF}'; break; case 'ack': if (!parameter.dst) return false; return '{R21' + parameter.dst + '50AC}'; break; case 'switchChannelRequest': //channel 17 fixed if (!parameter.panId) return false; return '{R04FFFFFF5060' + parameter.panId + '021100}'; // dst or FFFFFF??? break; case 'scanRequest': return '{R04FFFFFF7020' + parameter.panId + '02}'; break; case 'scanResponse': if (!parameter.panId || !parameter.dst) return false; return '{R01' + parameter.dst + '7021' + parameter.panId + '02}'; //fixed to deviceType 02 for now break; case 'beckonRequest': if (!parameter.dst) return false; return '{R06' + parameter.dst + '7050}'; break; case 'controlRequest': if (!parameter.dst || isNaN(parameter.position) || isNaN(parameter.angle)) return false; return '{R06' + parameter.dst + '7070' + '03' + ('0' + (Math.min(Math.max(parameter.position, 0), 100) * 2).toString(16)).substr(-2).toUpperCase() + ('0' + (Math.min(Math.max(parameter.angle, 0), 90) + 127).toString(16)).substr(-2).toUpperCase() + 'FFFF}'; //no idea how valance works break; case 'parameterGetRequest': if (!parameter.dst || !parameter.parameter) return false; return '{R06' + parameter.dst + '8010' + parameter.parameter + '}'; break; case 'parameterGetRequestPosition': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '01000005}'; break; case 'parameterGetRequestClock': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '26000046}'; break; case 'parameterGetRequestAutoSettings': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '0C000006}'; break; case 'parameterSetRequestAutoSettings': if (!parameter.dst || !parameter.parameter || isNaN(parameter.wind) || isNaN(parameter.rain) || isNaN(parameter.sun) || isNaN(parameter.dusk)) return false; return '{R06' + parameter.dst + '8020' + '0D000004' + ('0' + Math.min(Math.max(parameter.wind, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.rain, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.sun, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.dusk, 0), 9).toString(16)).substr(-2).toUpperCase() + (parameter.op ? '01' : '00') + '}'; break; case 'parameterSetRequestAutoAll': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8020' + '0D040001' + (parameter.op ? '01' : '00') + '}'; break; default: //unkown message type return false; break; } }
-
Hab mal alles in eine Klasse gepackt. Die ist bei weitem noch nicht fertig und da kann einiges drin sein was man eigentlich so nicht macht. Wie gesagt hab ewig nichts mit JavaScript gemacht und mir das heute soweit nur angelesen.
Im Decoder hab ich heute noch den joinNetworkRequest und scanRequest erfolgreich getestet.
Der Temperaturfühler der Wetterstation ist sehr träge. Fön und Eiswürfel haben nicht wirklich was gebracht.
Encoder hab ich noch nichts großartig angeschaut.
'use strict'; const SerialPort = require('serialport'); const Delimiter = SerialPort.parsers.Delimiter; const EventEmitter = require('events'); class wms extends EventEmitter { constructor() { super(); this.isOpen = false; this.queue = []; } decodeWMS(packet) { var obj = {}; switch (packet.substr(0, 1)) { case 'g': obj.type = 'stickType'; obj.payload = {name: packet.substr(1)}; break; case 'v': obj.type = 'stickVersion'; obj.payload = {version: packet.substr(1)}; break; case 'f': obj.type = 'error'; break; case 'a': obj.type = 'ack'; break; case 'r': obj.type = 'message'; obj.payload = this.decodeWMSMessage(packet.substr(1)); break; default: obj.type = 'unknown'; obj.payload = packet.substr(1); } return obj; }; decodeWMSMessage(message) { var obj = {}; obj.src = message.substr(0, 6); var type = message.substr(6, 4); var payload = message.substr(10); switch (type) { case '5018': obj.type = 'joinNetworkRequest'; obj.messagePayload = { panId: payload.substr(0, 4), networkKey: payload.substr(4, 32).match(/../g).reverse().join(""), unknown: payload.substr(36, 2), channel: parseInt(payload.substr(38, 2), 16) }; break; case '5060': obj.type = 'switchChannelRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), channel: parseInt(payload.substr(6, 2), 16) }; break; case '50AC': obj.type = 'ack'; obj.messagePayload = { unknown: payload.substr(0, 4) }; break; case '7020': obj.type = 'scanRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2) }; break; case '7021': obj.type = 'scanResponse'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), //63: wetterstation, 06: webcontrol, 02: stick/software, 20: zwischenstecker unknown: payload.substr(6) //optional }; break; case '7080': obj.type = 'weatherBroadcast'; obj.messagePayload = { unknown_1: payload.substr(0, 2), wind: parseInt(payload.substr(2, 2), 16), lumen: payload.substr(4, 2) === '00' ? parseInt(payload.substr(12, 2), 16) * 2 : parseInt(payload.substr(4, 2), 16) * parseInt(payload.substr(12, 2), 16), unknown_2: payload.substr(6, 6), unknown_3: payload.substr(14, 2), rain: payload.substr(16, 2) === 'C8', temp: parseInt(payload.substr(18, 2), 16) / 10, unknown_4: payload.substr(20) }; break; case '7050': obj.type = 'beckonRequest'; break; case '7070': obj.type = 'controlRequest'; obj.messagePayload = { unknown: payload.substr(0, 2), position: parseInt(payload.substr(2, 2), 16) / 2, angle: parseInt(payload.substr(4, 2), 16) - 127, valance_1: payload.substr(6, 2), valance_2: payload.substr(8, 2) }; break; case '7071': obj.type = 'controlResponse'; obj.messagePayload = payload; break; case '8010': obj.type = 'parameterGetRequest'; obj.messagePayload = { parameter: payload.substr(0) //01000005: position, 26000046: clock timer settings, 0C000006: auto modes & limits }; break; case '8011': obj.type = 'parameterGetResponse'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '01000003': //position case '01000005': //position obj.messagePayload.type = 'position'; obj.messagePayload.position = parseInt(payload.substr(8, 2), 16) / 2; obj.messagePayload.angle = parseInt(payload.substr(10, 2), 16) - 127; obj.messagePayload.valance_1 = payload.substr(12, 2); obj.messagePayload.valance_2 = payload.substr(14, 2); break; case '0C000006': //auto modes & limits obj.messagePayload.type = 'autoSettings'; obj.messagePayload.wind = parseInt(payload.substr(8, 2), 16); obj.messagePayload.rain = parseInt(payload.substr(10, 2), 16); obj.messagePayload.sun = parseInt(payload.substr(12, 2), 16); obj.messagePayload.dusk = parseInt(payload.substr(14, 2), 16); obj.messagePayload.op = parseInt(payload.substr(16, 2), 16); break; case '26000046': obj.messagePayload.type = 'clock'; obj.messagePayload.unknown = payload.substr(8); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; case '8020': obj.type = 'parameterSetRequest'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '0B080009': obj.messagePayload.type = 'clock'; obj.messagePayload.year = parseInt(payload.substr(8, 2), 16); obj.messagePayload.month = parseInt(payload.substr(10, 2), 16); obj.messagePayload.day = parseInt(payload.substr(12, 2), 16); obj.messagePayload.hour = parseInt(payload.substr(14, 2), 16); obj.messagePayload.minute = parseInt(payload.substr(16, 2), 16); obj.messagePayload.second = parseInt(payload.substr(18, 2), 16); obj.messagePayload.day_of_week = parseInt(payload.substr(20, 2), 16); obj.messagePayload.unknown = payload.substr(22); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; default: obj.type = 'unknown'; obj.messagePayload = payload; } return obj; }; encodeWMS(type, parameter) { if (!parameter) parameter = {}; switch (type) { case 'switchChannel': if (isNaN(parameter.channel)) return false; return '{M%' + parameter.channel + 'FFFF}'; break; case 'ack': if (!parameter.dst) return false; return '{R21' + parameter.dst + '50AC}'; break; case 'switchChannelRequest': //channel 17 fixed if (!parameter.panId) return false; return '{R04FFFFFF5060' + parameter.panId + '021100}'; // dst or FFFFFF??? break; case 'scanRequest': return '{R04FFFFFF7020' + parameter.panId + '02}'; break; case 'scanResponse': if (!parameter.panId || !parameter.dst) return false; return '{R01' + parameter.dst + '7021' + parameter.panId + '02}'; //fixed to deviceType 02 for now break; case 'beckonRequest': if (!parameter.dst) return false; return '{R06' + parameter.dst + '7050}'; break; case 'controlRequest': if (!parameter.dst || isNaN(parameter.position) || isNaN(parameter.angle)) return false; return '{R06' + parameter.dst + '7070' + '03' + ('0' + (Math.min(Math.max(parameter.position, 0), 100) * 2).toString(16)).substr(-2).toUpperCase() + ('0' + (Math.min(Math.max(parameter.angle, 0), 90) + 127).toString(16)).substr(-2).toUpperCase() + 'FFFF}'; //no idea how valance works break; case 'parameterGetRequest': if (!parameter.dst || !parameter.parameter) return false; return '{R06' + parameter.dst + '8010' + parameter.parameter + '}'; break; case 'parameterGetRequestPosition': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '01000005}'; break; case 'parameterGetRequestClock': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '26000046}'; break; case 'parameterGetRequestAutoSettings': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '0C000006}'; break; case 'parameterSetRequestAutoSettings': if (!parameter.dst || !parameter.parameter || isNaN(parameter.wind) || isNaN(parameter.rain) || isNaN(parameter.sun) || isNaN(parameter.dusk)) return false; return '{R06' + parameter.dst + '8020' + '0D000004' + ('0' + Math.min(Math.max(parameter.wind, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.rain, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.sun, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.dusk, 0), 9).toString(16)).substr(-2).toUpperCase() + (parameter.op ? '01' : '00') + '}'; break; case 'parameterSetRequestAutoAll': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8020' + '0D040001' + (parameter.op ? '01' : '00') + '}'; break; default: //unkown message type return false; break; } } onData(data) { var message = this.decodeWMS(data.toString('ascii',1)); switch(message.type) { case 'stickType': var next = this.getExpectedMessage('stickType'); if(next != undefined) { next.resolve(message); } break; case 'stickVersion': var next = this.getExpectedMessage('stickVersion'); if(next != undefined) { next.resolve(message.payload.version); } break; case 'error': case 'ack': var next = this.getExpectedMessage('ack'); if(next != undefined) { if(message.type === 'ack') { next.resolve(); } else { next.reject(new Error('WMS Stick rejected the command')); } } break; case 'message': switch(message.payload.type) { case 'weatherBroadcast': this.emit('weatherBroadcast', message); break; case 'parameterGetResponse': var next = this.getExpectedMessage('message', {'src': message.payload.src, 'msgType': message.payload.type, 'parameterType': message.payload.messagePayload.type}); if(next != undefined) { next.resolve(message); } break; } break; default: this.emit('unknown', message); } }; open(path) { var promise = new Promise( (resolve, reject) => { this.port = new SerialPort(path, {baudRate: 125000}, (error) => { if(error) { reject(error); return; } this.parser = this.port.pipe(new Delimiter( {delimiter: '}'})); this.parser.on('data', (data) => {this.onData(data)} ); resolve(); }); }); return promise.then(() => { return this.getStickType(); }, (error) => { this.isOpen = false; return Promise.reject(error); }).then((wms) => { this.isOpen = true; return Promise.resolve(); }, (error) => { this.isOpen = false; return Promise.reject(error); }); }; close() { this.port.drain(() => { this.port.close(); this.isOpen = false; }); } getExpectedMessage(type, parameter) { if(this.queue[type] == undefined) { this.queue[type] = []; } if(type !== 'message') { return(this.queue[type].shift()); } else { if(this.queue[type][parameter.src] == undefined) { this.queue[type][parameter.src] = []; } if(this.queue[type][parameter.src][parameter.msgType] == undefined) { this.queue[type][parameter.src][parameter.msgType] = []; } if(parameter.msgType === 'parameterGetResponse') { if(this.queue[type][parameter.src][parameter.msgType][parameter.parameterType] == undefined) { this.queue[type][parameter.src][parameter.msgType][parameter.parameterType] = []; } return this.queue[type][parameter.src][parameter.msgType][parameter.parameterType].shift(); } else { return this.queue[type][parameter.src][parameter.msgType].shift(); } } } addExpectedMessage(type, ms, parameter) { let promise = new Promise( (resolve, reject) => { if(this.queue[type] == undefined) { this.queue[type] = []; } if(type !== 'message') { this.queue[type].push({'resolve': resolve, 'reject': reject, 'parameter': parameter}); } else { if(this.queue[type][parameter.src] == undefined) { this.queue[type][parameter.src] = []; } if(this.queue[type][parameter.src][parameter.msgType] == undefined) { this.queue[type][parameter.src][parameter.msgType] = []; } if(parameter.msgType === 'parameterGetResponse') { if(this.queue[type][parameter.src][parameter.msgType][parameter.parameterType] == undefined) { this.queue[type][parameter.src][parameter.msgType][parameter.parameterType] = []; } this.queue[type][parameter.src][parameter.msgType][parameter.parameterType].push({'resolve': resolve, 'reject': reject, 'parameter': parameter}); } else { this.queue[type][parameter.src][parameter.msgType].push({'resolve': resolve, 'reject': reject, 'parameter': parameter}); } } }); let timeout = new Promise((resolve, reject) => { let id = setTimeout(() => { clearTimeout(id); this.getExpectedMessage(type, parameter); reject('Message ' + type + ' timedout in ' + ms + ' ms') }, ms) }); return Promise.race([ promise, timeout ]); } getStickType() { let promise = this.addExpectedMessage('stickType', 100); this.port.write('{G}'); return promise; } getStickVersion() { let promise = this.addExpectedMessage('stickVersion', 100); this.port.write('{V}'); return promise; } getPosition(dst) { let promises = []; promises.push(this.addExpectedMessage('ack', 100)); promises.push(this.addExpectedMessage('message', 200, {'src': dst, 'msgType': 'parameterGetResponse', 'parameterType': 'position'})); this.port.write(this.encodeWMS('parameterGetRequestPosition', {'dst': dst})); return Promise.all(promises); } static list() { return SerialPort.list(). then( (ports) => { var portPromises = []; ports.forEach( (port) => { portPromises.push(new Promise( (resolve, reject) => { let tempWMS = new wms(); tempWMS.open(port.comName).then( () => { tempWMS.close(); resolve({'comName': port.comName, 'isWMSStick': true}); }, (error) => { tempWMS.close(); resolve({'comName': port.comName, 'isWMSStick': false}) }); })); }); return Promise.all(portPromises); }, (error) =>{ return Promise.rejected(error); }); }; }; const myWMS = new wms(); wms.list().then((lst) => { console.log(lst); setTimeout(function () { myWMS.open('COM3').then(() => { myWMS.getStickVersion().then(console.log, console.error); myWMS.getPosition('AAAAAA').then(console.log, console.error); }, console.error); myWMS.on('weatherBroadcast', (msg) => {console.log(msg)}); }, 10000); }, console.error);
-
Die Klasse werde ich mir mal angucken.
Ich habe kleinere Verbesserungen an Decode und Encoder vorgenommen.
Weiterhin habe ich in den technischen Daten für meine Wetterstation PLUS gefunden, dass der Messbereich Helligkeit im Bereich 0-100klx und bei Dämmerung im Bereich 0-500lx liegt. Das wären das wohl genau die beiden Fälle, welche du unterscheidest, wobei ich bei klx den Wert noch mal 2 nehmen musste. Heute bei Sonne hat sich der Wert nämlich bei 50klx an die Wand gefahren (C8FFFFFFFAF).
Temperatur kommt auch noch nicht wirklich hin, da muss die Formel eine andere sein. Das Ergebnis sollte laut den technischen Daten zwischen -25°C und 60°C liegen. Wobei auch hier evtl. gar nicht der ganze Wertebereich (00-FF) genutzt wird. Eine sehr einfache Rechnung, welche aber für meine bisherigen Messungen mit einem anderen Außenthermometer übereinstimmen wäre: WERT / 2 - 35.
Wind könnte WERT / 10 sein (0-25m/s).
Technische Daten:
https://www.warema.de/Produkte/Steuerun … n_plus.php
Hier nochmal die aktualisierten Funktionen:
function decodeWMS(packet) { var obj = {}; switch (packet.substr(0, 1)) { case 'g': obj.type = 'stickType'; obj.payload = {name: packet.substr(1)}; break; case 'v': obj.type = 'stickVersion'; obj.payload = {version: packet.substr(1)}; break; case 'f': obj.type = 'error'; break; case 'a': obj.type = 'ack'; break; case 'r': obj.type = 'message'; obj.payload = decodeWMSMessage(packet.substr(1)); break; default: obj.type = 'unknown'; obj.payload = packet.substr(1); } return obj; } function decodeWMSMessage(message) { var obj = {}; obj.src = message.substr(0, 6); var type = message.substr(6, 4); var payload = message.substr(10); switch (type) { case '5018': obj.type = 'joinNetworkRequest'; obj.messagePayload = { panId: payload.substr(0, 4), networkKey: payload.substr(4, 32).match(/../g).reverse().join(""), unknown: payload.substr(36, 2), channel: parseInt(payload.substr(38, 2), 16) }; break; case '5060': obj.type = 'switchChannelRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), channel: parseInt(payload.substr(6, 2), 16) }; break; case '50AC': obj.type = 'ack'; obj.messagePayload = { unknown: payload.substr(0, 4) }; break; case '7020': obj.type = 'scanRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2) }; break; case '7021': obj.type = 'scanResponse'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), //63: wetterstation, 06: webcontrol, 02: stick/software, 20: zwischenstecker unknown: payload.substr(6) //optional }; break; case '7080': obj.type = 'weatherBroadcast'; obj.messagePayload = { unknown_1: payload.substr(0, 2), wind: parseInt(payload.substr(2, 2), 16) / 10, lumen: payload.substr(4, 2) === '00' ? parseInt(payload.substr(12, 2), 16) * 2 : parseInt(payload.substr(4, 2), 16) * parseInt(payload.substr(12, 2), 16) * 2, unknown_2: payload.substr(6, 6), unknown_3: payload.substr(14, 2), rain: payload.substr(16, 2) === 'C8', temp: parseInt(payload.substr(18, 2), 16) / 2 - 35, unknown_4: payload.substr(20) }; break; case '7050': obj.type = 'beckonRequest'; break; case '7070': obj.type = 'controlRequest'; obj.messagePayload = { unknown: payload.substr(0, 2), position: parseInt(payload.substr(2, 2), 16) / 2, angle: parseInt(payload.substr(4, 2), 16) - 127, valance_1: payload.substr(6, 2), valance_2: payload.substr(8, 2) }; break; case '7071': obj.type = 'controlResponse'; obj.messagePayload = payload; break; case '8010': obj.type = 'parameterGetRequest'; obj.messagePayload = { parameter: payload.substr(0) //01000005: position, 26000046: clock timer settings, 0C000006: auto modes & limits }; break; case '8011': obj.type = 'parameterGetResponse'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '01000003': //position case '01000005': //position obj.messagePayload.type = 'position'; obj.messagePayload.position = parseInt(payload.substr(8, 2), 16) / 2; obj.messagePayload.angle = parseInt(payload.substr(10, 2), 16) - 127; obj.messagePayload.valance_1 = payload.substr(12, 2); obj.messagePayload.valance_2 = payload.substr(14, 2); break; case '0C000006': //auto modes & limits obj.messagePayload.type = 'autoSettings'; obj.messagePayload.wind = parseInt(payload.substr(8, 2), 16); obj.messagePayload.rain = parseInt(payload.substr(10, 2), 16); obj.messagePayload.sun = parseInt(payload.substr(12, 2), 16); obj.messagePayload.dusk = parseInt(payload.substr(14, 2), 16); obj.messagePayload.op = parseInt(payload.substr(16, 2), 16); break; case '26000046': obj.messagePayload.type = 'clock'; obj.messagePayload.unknown = payload.substr(8); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; case '8020': obj.type = 'parameterSetRequest'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '0B080009': obj.messagePayload.type = 'clock'; obj.messagePayload.year = parseInt(payload.substr(8, 2), 16); obj.messagePayload.month = parseInt(payload.substr(10, 2), 16); obj.messagePayload.day = parseInt(payload.substr(12, 2), 16); obj.messagePayload.hour = parseInt(payload.substr(14, 2), 16); obj.messagePayload.minute = parseInt(payload.substr(16, 2), 16); obj.messagePayload.second = parseInt(payload.substr(18, 2), 16); obj.messagePayload.day_of_week = parseInt(payload.substr(20, 2), 16); obj.messagePayload.unknown = payload.substr(22); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; default: obj.type = 'unknown'; obj.messagePayload = payload; } return obj; } function encodeWMS(type, parameter) { if (!parameter) parameter = {}; switch (type) { case 'setKey': if (!parameter.key) return false; return '{K401' + parameter.key + '}'; break; case 'setScanMode': if (isNaN(parameter.channel) || !parameter.panId) return false; return '{M#' + parameter.channel + parameter.panId.match(/../g).reverse().join("") + '}'; break; case 'switchChannel': if (isNaN(parameter.channel) || !parameter.panId) return false; return '{M%' + parameter.channel + parameter.panId + '}'; break; case 'ack': if (!parameter.dst) return false; return '{R21' + parameter.dst + '50AC}'; break; case 'switchChannelRequest': //channel 17 fixed if (!parameter.panId) return false; return '{R04FFFFFF5060' + parameter.panId + '021100}'; // dst or FFFFFF??? break; case 'scanRequest': return '{R04FFFFFF7020' + parameter.panId + '02}'; break; case 'scanResponse': if (!parameter.panId || !parameter.dst) return false; return '{R01' + parameter.dst + '7021' + parameter.panId + '02}'; //fixed to deviceType 02 for now break; case 'beckonRequest': if (!parameter.dst) return false; return '{R06' + parameter.dst + '7050}'; break; case 'controlRequest': if (!parameter.dst || isNaN(parameter.position) || isNaN(parameter.angle)) return false; return '{R06' + parameter.dst + '7070' + '03' + ('0' + (Math.min(Math.max(parameter.position, 0), 100) * 2).toString(16)).substr(-2).toUpperCase() + ('0' + (Math.min(Math.max(parameter.angle, 0), 90) + 127).toString(16)).substr(-2).toUpperCase() + 'FFFF}'; //no idea how valance works break; case 'parameterGetRequest': if (!parameter.dst || !parameter.parameter) return false; return '{R06' + parameter.dst + '8010' + parameter.parameter + '}'; break; case 'parameterGetRequestPosition': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '01000005}'; break; case 'parameterGetRequestClock': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '26000046}'; break; case 'parameterGetRequestAutoSettings': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '0C000006}'; break; case 'parameterSetRequestAutoSettings': if (!parameter.dst || !parameter.parameter || isNaN(parameter.wind) || isNaN(parameter.rain) || isNaN(parameter.sun) || isNaN(parameter.dusk)) return false; return '{R06' + parameter.dst + '8020' + '0D000004' + ('0' + Math.min(Math.max(parameter.wind, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.rain, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.sun, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.dusk, 0), 9).toString(16)).substr(-2).toUpperCase() + (parameter.op ? '01' : '00') + '}'; break; case 'parameterSetRequestAutoAll': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8020' + '0D040001' + (parameter.op ? '01' : '00') + '}'; break; default: //unkown message type return false; break; } }
-
Ja, Formel für die Temperatur könnte hinkommen. Hab jetzt auch noch mal mit einem anderen Thermometer geprüft.
Wie ich meine erste eingebaut habe waren es um die 10° hätte dann hingehauen
Für Wind gehe ich davon aus das keine Umrechnung nötig ist. Für Regen haben sie es auch nicht gemacht,
da kommen genau die 200 die auch in der Dokumentation stehen.
-
Temperatur fällt im moment ab und nach meiner Formel weiterhin alles synchron mit dem anderen Thermometer, natürlich nicht aufs zehntel Grad genau.
Wind durch 10 wäre tatsächlich zu wenig, andersrum kommt es mir ohne aber auch zu viel vor. 5-6m/s bei quasi Windstille. Kann natürlich auch sein, dass der Sensor bei sehr wenig Wind nicht gut funktioniert, die Wetterstation Plus hat keinen mechanischen Sensor dafür.
-
So ich hatte nun ein wenig Zeit und habe mir mal ein komplettes Skript für ioBroker.javascript gebastelt um alles zu testen.
So weit funktioniert alles, allerdings ist mir aufgefallen, dass Kanäle noch komplett fehlen.
Daher wollte ich mal nachfragen, ob du dazu irgendwelche Infos hast?
Ich nehme an zum auslesen und setzen von Kanalzugehörigkeiten eines Gerätes wird einen weiteren Parameter geben.
Die Frage ist, wie man an einen Kanal sendet, evtl. per Broadcast?
-
Bin in den letzten Tagen auch nicht zu viel gekommen. Wollte noch die Messwerte der Wetterstation mit dem Handsender überprüfen allerdings war der Wind nie Stark genug und ich immer zu Spät um die Lumen noch ordentlich zu prüfen…
Ansonsten verstehe ich mittlerweile ein paar Byte der Control Response.
Hast du in deinem Script meine Klasse verwendet oder etwas eigenes geschrieben?
Zum Thema Kanäle: Da bin ich mir ziemlich sicher das diese nur im Handsender bzw WebControl existieren. Wenn der Handsender an einen Kanal mit 4 Aktoren einen Befehl schickt leuchtet die sende LED genau 4 mal. Bei einem WebControl Kanal mit 2 Aktoren hab ich da entsprechend dann auch 2 Nachrichten an die einzelnen Aktoren mit dem ConBee mit geschnitten. Um ganz sicher zu sein könnten wir einem Handsender beim einlernen des Sticks mal vorgaukeln der Stick sei ein Zwischenstecker. Dann könnten wir den Stick im Handsender einem Kanal zuordnen. Bin mir ziemlich sicher das der Stick in diesem Fall nicht mitgeteilt bekommt auf welchen Kanal er programmiert wurde.
-
Das mit den Kanälen klingt plausibel. Ich denke mittlerweile, der Windwert funktioniert wie von die beschrieben, bei Temperatur kommt meine Formel zumindest ungefähr hin. Es könnte natürlich eine richtigere Formel (mit sehr ähnlichen) Ergebnissen geben.
Ich habe für das Skript zum testen einfach die Funktionen zusammengewüftelt, müsste aber leicht auf deine Klasse übertragbar sein bzw. die relevanten Funktionen einfach in eine Klasse übertragbar sein. Ich poste das Skript dir hier einfach mal.
Die Funktionalität steckt im Prinzip komplett in processWMS, wo auf die ankommenden Packete entsprechend reagiert wird. In dieser Funktion wird dann natürlich auch massiv gebrauch von der ioBroker-API gemacht, also Datenpunkte erstellt und verändert usw..
Verwendung des Skripts:
Mit den default-Werten für PANID und KEY müsste sich das Skript im Anlernmodus befinden, kann also über Handsender eingelernt werden, KEY und PANID werden dabei im log ausgegeben.
Sobald PANID und KEY im Skript hinterlegt werden, wird der Stick damit versorgt und fragt beim Start (und in regelmäßigen Abständen) Raffstore und Wetterstationen ab, speichert die Werte in Datenpunkten und reagiert umgekehrt auf Änderungen in den Datenpunkten 'level' und 'angle'.
var namespace = 'WMS'; var SerialPort = require('serialport'); //config const PATH = "/dev/ttyUSB0"; const CHANNEL = 17; const PANID = "FFFF"; //inclusion mode: FFFF const KEY = "00112233445566778899AABBCCDDEEFF"; //inclusion mode: "00112233445566778899AABBCCDDEEFF" var positionInterval = 60; //how often current position is requested (seconds) var scanInterval = 600; //how often to scan for devices (seconds) //listPorts(); //uncomment to list all available serial ports /* do not edit below! */ //globals var knownDevices = {}; //stores known devices var lastData = ''; //contains last packet var writeQueue = []; //stores data to be sent to serial port var timers = {}; /* open serial port and setup parser */ function init() { //scan 3 times setTimeout(function () { wmsScan(); }, 5000); setTimeout(function () { wmsScan(); }, 10000); setTimeout(function () { wmsScan(); }, 30000); //scan again every scanInterval seconds setInterval(function () { wmsScan(); }, scanInterval * 1000); } //connect to serial port const port = new SerialPort(PATH, { baudRate: 125000, parity: 'none', dataBits: 8, stopBits: 1, autoOpen: false, }); //create parser with '}' as delemiter const parser = port.pipe(new SerialPort.parsers.Readline({delimiter: '}'})); // handle serial port errors port.on('error', function () { log('serial port error!', 'warn'); closePort(); }); //parse incomming packets parser.on('data', parseData); //open serial port portOpen().then((msg) => { log(msg); writeAndWaitFor('{G}', 'gWMS USB-Stick', true).then((line) => { return writeAndWaitFor('{V}', 'v', true); }).then((line) => { log('Stick Version: ' + line); return writeAndWaitFor(encodeWMS('setKey', {key: KEY}), 'a', true); }).then((line) => { return writeAndWaitFor(encodeWMS('switchChannel', { channel: CHANNEL, panId: PANID }), 'a', true); }).then((line) => { init(); }).catch((err) => { log(err, 'warn'); closePort(); }); }).catch((err) => { log(err, 'warn'); }); /* serialport helper functions */ //opens port with promise function portOpen() { return new Promise((resolve, reject) => { port.open((err) => { err ? reject(err) : resolve('port opened'); }) }); } //close port if open function closePort() { return new Promise((resolve, reject) => { log('closing open serial ports', 'warn'); if (port && port.isOpen) { // close connection port.close(() => { resolve('port closed') }); } else { reject('no port was opened'); } }); } //on script stop close port onStop(closePort, 2000); //handle incomming data function parseData(data) { //trim data data = wmsTrim(data); //do nothing, if packet is received twice if (lastData === data) return lastData = data; log('received message: ' + data, 'debug'); //decode data into object var obj = decodeWMS(data); log(JSON.stringify(obj), 'debug'); //process object processWMS(obj); } //list available serial ports function listPorts() { SerialPort.list().then((ports) => { log('Serial Ports: ' + JSON.stringify(ports)); }).catch((err) => { log('error listing ports: ' + JSON.stringify(err)); }); } //write to serial port and wait for answer function writeAndWaitFor(data, expect, rejectOnTimeout, timeout) { return new Promise((resolve, reject) => { if (isNaN(timeout)) timeout = 5000; log('sending ' + data, 'debug'); listener = (line) => { if (wmsTrim(line).substr(0, expect.length) === expect) { log('received expected answer: ' + expect, 'debug'); parser.removeListener('data', listener); resolve(wmsTrim(line)); } else { log('received unexpected answer (still waiting): ' + wmsTrim(line).substr(0, expect.length) + '!=' + expect, 'debug'); } }; parser.on('data', listener); enqueue(data); //remove listener after 5 seconds setTimeout(() => { parser.removeListener('data', listener); rejectOnTimeout ? reject(expect) : resolve(false); }, timeout); }); } function enqueue(data) { if (typeof data === 'string') writeQueue.push(data); if (writeQueue.length === 1) { port.write(writeQueue.shift()); port.drain((err) => { if (writeQueue.length) enqueue(); }); } } /* WMS helper functions */ //trim wms string function wmsTrim(data) { return data.trim().substr(1); } //decode wms strings into an object function decodeWMS(packet) { var obj = {}; switch (packet.substr(0, 1)) { case 'g': obj.type = 'stickType'; obj.payload = {name: packet.substr(1)}; break; case 'v': obj.type = 'stickVersion'; obj.payload = {version: packet.substr(1)}; break; case 'f': obj.type = 'error'; break; case 'a': obj.type = 'ack'; break; case 'r': obj.type = 'message'; obj.payload = decodeWMSMessage(packet.substr(1)); break; default: obj.type = 'unknown'; obj.payload = packet.substr(1); } return obj; } //decode wms messages into an object function decodeWMSMessage(message) { var obj = {}; obj.src = message.substr(0, 6); var type = message.substr(6, 4); var payload = message.substr(10); switch (type) { case '5018': obj.type = 'joinNetworkRequest'; obj.messagePayload = { panId: payload.substr(0, 4), networkKey: payload.substr(4, 32).match(/../g).reverse().join(""), unknown: payload.substr(36, 2), channel: parseInt(payload.substr(38, 2), 16) }; break; case '5060': obj.type = 'switchChannelRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), channel: parseInt(payload.substr(6, 2), 16) }; break; case '50AC': obj.type = 'ack'; obj.messagePayload = { unknown: payload.substr(0, 4) }; break; case '7020': obj.type = 'scanRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2) }; break; case '7021': obj.type = 'scanResponse'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), //63: wetterstation, 06: webcontrol, 02: stick/software, 20: zwischenstecker unknown: payload.substr(6) //optional }; break; case '7080': obj.type = 'weatherBroadcast'; obj.messagePayload = { unknown_1: payload.substr(0, 2), wind: parseInt(payload.substr(2, 2), 16), lumen: payload.substr(4, 2) === '00' ? parseInt(payload.substr(12, 2), 16) * 2 : parseInt(payload.substr(4, 2), 16) * parseInt(payload.substr(12, 2), 16) * 2, unknown_2: payload.substr(6, 6), unknown_3: payload.substr(14, 2), rain: payload.substr(16, 2) === 'C8', temp: parseInt(payload.substr(18, 2), 16) / 2 - 35, unknown_4: payload.substr(20) }; break; case '7050': obj.type = 'beckonRequest'; break; case '7070': obj.type = 'controlRequest'; obj.messagePayload = { unknown: payload.substr(0, 2), position: parseInt(payload.substr(2, 2), 16) / 2, angle: parseInt(payload.substr(4, 2), 16) - 127, valance_1: payload.substr(6, 2), valance_2: payload.substr(8, 2) }; break; case '7071': obj.type = 'controlResponse'; obj.messagePayload = payload; break; case '8010': obj.type = 'parameterGetRequest'; obj.messagePayload = { parameter: payload.substr(0) //01000005: position, 26000046: clock timer settings, 0C000006: auto modes & limits }; break; case '8011': obj.type = 'parameterGetResponse'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '01000003': //position case '01000005': //position obj.messagePayload.type = 'position'; obj.messagePayload.position = parseInt(payload.substr(8, 2), 16) / 2; obj.messagePayload.angle = parseInt(payload.substr(10, 2), 16) - 127; obj.messagePayload.valance_1 = payload.substr(12, 2); obj.messagePayload.valance_2 = payload.substr(14, 2); break; case '0C000006': //auto modes & limits obj.messagePayload.type = 'autoSettings'; obj.messagePayload.wind = parseInt(payload.substr(8, 2), 16); obj.messagePayload.rain = parseInt(payload.substr(10, 2), 16); obj.messagePayload.sun = parseInt(payload.substr(12, 2), 16); obj.messagePayload.dusk = parseInt(payload.substr(14, 2), 16); obj.messagePayload.op = parseInt(payload.substr(16, 2), 16); break; case '26000046': obj.messagePayload.type = 'clock'; obj.messagePayload.unknown = payload.substr(8); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; case '8020': obj.type = 'parameterSetRequest'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '0B080009': obj.messagePayload.type = 'clock'; obj.messagePayload.year = parseInt(payload.substr(8, 2), 16); obj.messagePayload.month = parseInt(payload.substr(10, 2), 16); obj.messagePayload.day = parseInt(payload.substr(12, 2), 16); obj.messagePayload.hour = parseInt(payload.substr(14, 2), 16); obj.messagePayload.minute = parseInt(payload.substr(16, 2), 16); obj.messagePayload.second = parseInt(payload.substr(18, 2), 16); obj.messagePayload.day_of_week = parseInt(payload.substr(20, 2), 16); obj.messagePayload.unknown = payload.substr(22); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; default: obj.type = 'unknown'; obj.messagePayload = payload; } return obj; } //create wms strings function encodeWMS(type, parameter) { if (!parameter) parameter = {}; switch (type) { case 'setKey': if (!parameter.key) return false; return '{K401' + parameter.key + '}'; break; case 'setScanMode': if (isNaN(parameter.channel) || !parameter.panId) return false; return '{M#' + parameter.channel + parameter.panId.match(/../g).reverse().join("") + '}'; break; case 'switchChannel': if (isNaN(parameter.channel) || !parameter.panId) return false; return '{M%' + parameter.channel + parameter.panId + '}'; break; case 'ack': if (!parameter.dst) return false; return '{R21' + parameter.dst + '50AC}'; break; case 'switchChannelRequest': //channel 17 fixed if (!parameter.panId) return false; return '{R04FFFFFF5060' + parameter.panId + '021100}'; // dst or FFFFFF??? break; case 'scanRequest': return '{R04FFFFFF7020' + parameter.panId + '02}'; break; case 'scanResponse': if (!parameter.panId || !parameter.dst) return false; return '{R01' + parameter.dst + '7021' + parameter.panId + '02}'; //fixed to deviceType 02 for now break; case 'beckonRequest': if (!parameter.dst) return false; return '{R06' + parameter.dst + '7050}'; break; case 'controlRequest': if (!parameter.dst || isNaN(parameter.position) || isNaN(parameter.angle)) return false; return '{R06' + parameter.dst + '7070' + '03' + ('0' + (Math.min(Math.max(parameter.position, 0), 100) * 2).toString(16)).substr(-2).toUpperCase() + ('0' + (Math.min(Math.max(parameter.angle, 0), 90) + 127).toString(16)).substr(-2).toUpperCase() + 'FFFF}'; //no idea how valance works break; case 'parameterGetRequest': if (!parameter.dst || !parameter.parameter) return false; return '{R06' + parameter.dst + '8010' + parameter.parameter + '}'; break; case 'parameterGetRequestPosition': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '01000005}'; break; case 'parameterGetRequestClock': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '26000046}'; break; case 'parameterGetRequestAutoSettings': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '0C000006}'; break; case 'parameterSetRequestAutoSettings': if (!parameter.dst || !parameter.parameter || isNaN(parameter.wind) || isNaN(parameter.rain) || isNaN(parameter.sun) || isNaN(parameter.dusk)) return false; return '{R06' + parameter.dst + '8020' + '0D000004' + ('0' + Math.min(Math.max(parameter.wind, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.rain, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.sun, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.dusk, 0), 9).toString(16)).substr(-2).toUpperCase() + (parameter.op ? '01' : '00') + '}'; break; case 'parameterSetRequestAutoAll': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8020' + '0D040001' + (parameter.op ? '01' : '00') + '}'; break; default: //unkown message type return false; break; } } //process packets function processWMS(obj) { //log(JSON.stringify(obj)); if (obj.type !== 'message') return; switch (obj.payload.type) { case 'switchChannelRequest': log('received switchChannelRequest, switching channel to ' + obj.payload.messagePayload.channel, 'debug'); writeAndWaitFor(encodeWMS('switchChannel', { channel: obj.payload.messagePayload.channel, panId: PANID }), 'a'); break; case 'scanRequest': // send scanResponse log('received scanRequest, sending scanResponse', 'debug'); writeAndWaitFor(encodeWMS('scanResponse', {dst: obj.payload.src, panId: PANID}), 'a'); break; case 'joinNetworkRequest': log('received joinNetworkRequest:', 'debug'); log('KEY: ' + obj.payload.messagePayload.networkKey); log('CHANNEL: ' + obj.payload.messagePayload.channel); log('PANID: ' + obj.payload.messagePayload.panId); writeAndWaitFor(encodeWMS('ack', {dst: obj.payload.src}), 'a'); break; case 'scanResponse': log('received scanResponse', 'debug'); log('TYPE: ' + obj.payload.messagePayload.deviceType, 'debug'); log('SNR:' + obj.payload.src, 'debug'); if (obj.payload.messagePayload.deviceType === '20') { createState(this.namespace + '.Raffstore.' + obj.payload.src + '.position', 0, false, { type: 'number', min: 0, max: 100, unit: '%' }); createState(this.namespace + '.Raffstore.' + obj.payload.src + '.angle', 0, false, { type: 'number', min: 0, max: 90, unit: '°' }, function () { if (knownDevices[obj.payload.src]) return; knownDevices[obj.payload.src] = true; var src = obj.payload.src; log('device type 20 found: ' + src); var deviceId = 'javascript.' + instance + '.' + this.namespace + '.Raffstore.' + src; on({id: deviceId + '.position', change: 'ne', ack: false}, function (obj) { //send parameter writeAndWaitFor( encodeWMS('controlRequest', { dst: src, position: obj.state.val, angle: getState(deviceId + '.angle').val }), 'r' + src + '7071' ).then(() => { var lastValue = getState(deviceId + '.position').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); var timer = setInterval(function () { //get parameter periodicaly until no change is detected log(getState(deviceId + '.position').val + ':' + lastValue, 'debug') if (getState(deviceId + '.position').val === lastValue) clearInterval(timer); lastValue = getState(deviceId + '.position').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, 3500); //setState(deviceId + '.position', getState(deviceId + '.position').val, true); }); }); on({id: deviceId + '.angle', change: 'ne', ack: false}, function (obj) { //send parameter writeAndWaitFor(encodeWMS('controlRequest', { dst: src, position: getState(deviceId + '.position').val, angle: obj.state.val }), 'r' + src + '7071' ).then(() => { var lastValue = getState(deviceId + '.angle').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); var timer = setInterval(function () { //get parameter periodicaly until no change is detected log(getState(deviceId + '.angle').val + ':' + lastValue, 'debug') if (getState(deviceId + '.angle').val === lastValue) clearInterval(timer); lastValue = getState(deviceId + '.angle').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, 3500); //setState(deviceId + '.angle', getState(deviceId + '.angle').val, true); }); }); setTimeout(function () { //get parameter once writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, 5000 + Math.random() * 5000); setInterval(function () { //get parameter periodicaly writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, positionInterval * 1000 + Math.random() * 5000); }); } break; case 'parameterGetResponse': log('received parameterGetResponse', 'debug'); switch (obj.payload.messagePayload.type) { case 'position': setStateDelayed(this.namespace + '.Raffstore.' + obj.payload.src + '.position', obj.payload.messagePayload.position, true, 100, true); setStateDelayed(this.namespace + '.Raffstore.' + obj.payload.src + '.angle', obj.payload.messagePayload.angle, true, 100, true); default: break; } break; case 'weatherBroadcast': log('received weatherBroadcast', 'debug'); createState(this.namespace + '.Wetter.' + obj.payload.src + '.temp', 0, false, { type: 'number', unit: '°C', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.temp', obj.payload.messagePayload.temp, true, 100, true); }); createState(this.namespace + '.Wetter.' + obj.payload.src + '.wind', 0, false, { type: 'number', min: 0, unit: 'm/s', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.wind', obj.payload.messagePayload.wind, true, 100, true); }); createState(this.namespace + '.Wetter.' + obj.payload.src + '.lux', 0, false, { type: 'number', min: 0, unit: 'lux', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.lux', obj.payload.messagePayload.lumen, true, 100, true); }); createState(this.namespace + '.Wetter.' + obj.payload.src + '.rain', false, false, { type: 'boolean', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.rain', obj.payload.messagePayload.rain, true, 100, true); }); break; default: break; } } //scan for devices function wmsScan() { writeAndWaitFor(encodeWMS('scanRequest', {panId: PANID}), 'a'); }
-
Wie sieht es eigtl. mit dem STOP-Befehl aus? Hast du da etwas?
-
Wirklich schade, dass anscheinend dieses Thema Warema Raffstoresteuerung nicht mehr weiterverfolgt wird und ein offizieller Adapter für den ioBroker geschaffen wird. Leider habe ich wenig Ahnung von der Programmierung aber ich kenne mindest 10 Leute die sich auch freuen würden.
-
Wirklich schade, dass anscheinend dieses Thema Warema Raffstoresteuerung nicht mehr weiterverfolgt wird und ein offizieller Adapter für den ioBroker geschaffen wird. Leider habe ich wenig Ahnung von der Programmierung aber ich kenne mindest 10 Leute die sich auch freuen würden. ` Geht mir leider genau so. Wäre auch dazu bereit mitzuhelfen wo ich nur kann oder auch Spenden wenn's sein muss
Gesendet von meinem HUAWEI VNS-L21 mit Tapatalk
-
Leider keine Zeit, das Skript ist aber so lauffähig!
-
kein Thema, aber auf jeden Fall vielen Dank für das Skript. Bei mir wird es erst im Sommer soweit sein, wird dann schon klappen
-
Pman trotzdem super, dass du es so weit schon geschrieben hast. Vielleicht findet sich ja ein anderer der die Fähigkeiten hat dieses Skript in einen Adapter umzusetzen. Würde mich mega freuen!
-
Pman trotzdem super, dass du es so weit schon geschrieben hast. Vielleicht findet sich ja ein anderer der die Fähigkeiten hat dieses Skript in einen Adapter umzusetzen. Würde mich mega freuen! `
Adapter wäre natürlich super und bequem, aber unterm Strich hast du genau das gleiche mit dem Skript, was ein Adapter machen würde. Also keine falsche Scheu! Wenn das Skript jemand nutzen möchte helfe ich gerne bei der Ersteinrichtung, falls es Probleme gibt. -
Pman, wärst du so nett und würdest uns einfach kurz zusammenfassen was man genau machen muss? Eine ganz kleine step by step Anleitung. Das ich beide scripte brauche, hab ich bereits verstanden, aber diese einfach in den iobroker reinpacken wird ja nicht reichen oder?
Eilt bei mir eh nicht wirklich, aber dafür wär ich / wir dir mega dankbar
-
Du benötigst den Javascript-Adapter und einen WMS-Stick.
Kurze Anleitung:
1. Falls nicht vorhanden den Javascript-Adapter installieren
2. Im Javascript Adapter bei "zusätzliche NPM-Module" "serialport" eintragen, dann sollte der Adapter neu starten und das serialport-Packet installieren, was ggf. etwas dauert.
3. Im Admin im Skripte-Tab Ein neues Skript (Javascript!) erstellen und das unten angefügte Skript einfügen, dabei die "//" vor "listPorts();" weg machen, damit beim ersten Start des Skripts die Seriellen Ports im LOG aufgelistet werden. Das Skript nun starten. Im Normalfall sollte der WMS-Stick auf einem Raspi wie bei mir unter /dev/ttyUSB0 zu finden sein, ansonsten muss man dies anpassen im Skript. Nachdem der Pfad zum WMS-Stick bekannt ist und eingetragen wurde "listPorts();" wieder auskommentieren. Das Skript neustarten.
4. CHANNEL, PANID und KEY müssen nun bestimmt werden, dazu ist eine WMS-Zentrale oder WMS-Fernbedienung nötig. Einfach wie in der jeweiligen Anleitung beschrieben ein neues Gerät in das Netzwerk aufnehmen, dabei sollte der WMS-Stick gefunden werden. Sobald man diesen in das Netzwerk aufnimmt werden im LOG des Skriptes die drei Werte angezeigt.
5. CHANNEL, PANID und KEY in das Skript eintrage und Skript neustarten.
6. Unter javascript.0.WMS sollten nun für alle gefunden Geräte (Motoren, Wetterstationen usw.) Datenpunkte angelegt werden, die Nutzung sollte halbwegs selbsterlärend sein (level, angle).
var namespace = 'WMS'; var SerialPort = require('serialport'); //config const PATH = "/dev/ttyUSB0"; const CHANNEL = 17; const PANID = "FFFF"; //inclusion mode: FFFF const KEY = "00112233445566778899AABBCCDDEEFF"; //inclusion mode: "00112233445566778899AABBCCDDEEFF" var positionInterval = 60; //how often current position is requested (seconds) var scanInterval = 600; //how often to scan for devices (seconds) //listPorts(); //uncomment to list all available serial ports /* do not edit below! */ //globals var knownDevices = {}; //stores known devices var lastData = ''; //contains last packet var writeQueue = []; //stores data to be sent to serial port var timers = {}; /* open serial port and setup parser */ function init() { //scan 3 times setTimeout(function () { wmsScan(); }, 5000); setTimeout(function () { wmsScan(); }, 10000); setTimeout(function () { wmsScan(); }, 30000); //scan again every scanInterval seconds setInterval(function () { wmsScan(); }, scanInterval * 1000); } //connect to serial port const port = new SerialPort(PATH, { baudRate: 125000, parity: 'none', dataBits: 8, stopBits: 1, autoOpen: false, }); //create parser with '}' as delemiter const parser = port.pipe(new SerialPort.parsers.Readline({delimiter: '}'})); // handle serial port errors port.on('error', function () { log('serial port error!', 'warn'); closePort(); }); //parse incomming packets parser.on('data', parseData); //open serial port portOpen().then((msg) => { log(msg); writeAndWaitFor('{G}', 'gWMS USB-Stick', true).then((line) => { return writeAndWaitFor('{V}', 'v', true); }).then((line) => { log('Stick Version: ' + line); return writeAndWaitFor(encodeWMS('setKey', {key: KEY}), 'a', true); }).then((line) => { return writeAndWaitFor(encodeWMS('switchChannel', { channel: CHANNEL, panId: PANID }), 'a', true); }).then((line) => { init(); }).catch((err) => { log(err, 'warn'); closePort(); }); }).catch((err) => { log(err, 'warn'); }); /* serialport helper functions */ //opens port with promise function portOpen() { return new Promise((resolve, reject) => { port.open((err) => { err ? reject(err) : resolve('port opened'); }) }); } //close port if open function closePort() { return new Promise((resolve, reject) => { log('closing open serial ports', 'warn'); if (port && port.isOpen) { // close connection port.close(() => { resolve('port closed') }); } else { reject('no port was opened'); } }); } //on script stop close port onStop(closePort, 2000); //handle incomming data function parseData(data) { //trim data data = wmsTrim(data); //do nothing, if packet is received twice if (lastData === data) return lastData = data; log('received message: ' + data, 'debug'); //decode data into object var obj = decodeWMS(data); log(JSON.stringify(obj), 'debug'); //process object processWMS(obj); } //list available serial ports function listPorts() { SerialPort.list().then((ports) => { log('Serial Ports: ' + JSON.stringify(ports)); }).catch((err) => { log('error listing ports: ' + JSON.stringify(err)); }); } //write to serial port and wait for answer function writeAndWaitFor(data, expect, rejectOnTimeout, timeout) { return new Promise((resolve, reject) => { if (isNaN(timeout)) timeout = 5000; log('sending ' + data, 'debug'); listener = (line) => { if (wmsTrim(line).substr(0, expect.length) === expect) { log('received expected answer: ' + expect, 'debug'); parser.removeListener('data', listener); resolve(wmsTrim(line)); } else { log('received unexpected answer (still waiting): ' + wmsTrim(line).substr(0, expect.length) + '!=' + expect, 'debug'); } }; parser.on('data', listener); enqueue(data); //remove listener after 5 seconds setTimeout(() => { parser.removeListener('data', listener); rejectOnTimeout ? reject(expect) : resolve(false); }, timeout); }); } function enqueue(data) { if (typeof data === 'string') writeQueue.push(data); if (writeQueue.length === 1) { port.write(writeQueue.shift()); port.drain((err) => { if (writeQueue.length) enqueue(); }); } } /* WMS helper functions */ //trim wms string function wmsTrim(data) { return data.trim().substr(1); } //decode wms strings into an object function decodeWMS(packet) { var obj = {}; switch (packet.substr(0, 1)) { case 'g': obj.type = 'stickType'; obj.payload = {name: packet.substr(1)}; break; case 'v': obj.type = 'stickVersion'; obj.payload = {version: packet.substr(1)}; break; case 'f': obj.type = 'error'; break; case 'a': obj.type = 'ack'; break; case 'r': obj.type = 'message'; obj.payload = decodeWMSMessage(packet.substr(1)); break; default: obj.type = 'unknown'; obj.payload = packet.substr(1); } return obj; } //decode wms messages into an object function decodeWMSMessage(message) { var obj = {}; obj.src = message.substr(0, 6); var type = message.substr(6, 4); var payload = message.substr(10); switch (type) { case '5018': obj.type = 'joinNetworkRequest'; obj.messagePayload = { panId: payload.substr(0, 4), networkKey: payload.substr(4, 32).match(/../g).reverse().join(""), unknown: payload.substr(36, 2), channel: parseInt(payload.substr(38, 2), 16) }; break; case '5060': obj.type = 'switchChannelRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), channel: parseInt(payload.substr(6, 2), 16) }; break; case '50AC': obj.type = 'ack'; obj.messagePayload = { unknown: payload.substr(0, 4) }; break; case '7020': obj.type = 'scanRequest'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2) }; break; case '7021': obj.type = 'scanResponse'; obj.messagePayload = { panId: payload.substr(0, 4), deviceType: payload.substr(4, 2), //63: wetterstation, 06: webcontrol, 02: stick/software, 20: zwischenstecker unknown: payload.substr(6) //optional }; break; case '7080': obj.type = 'weatherBroadcast'; obj.messagePayload = { unknown_1: payload.substr(0, 2), wind: parseInt(payload.substr(2, 2), 16), lumen: payload.substr(4, 2) === '00' ? parseInt(payload.substr(12, 2), 16) * 2 : parseInt(payload.substr(4, 2), 16) * parseInt(payload.substr(12, 2), 16) * 2, unknown_2: payload.substr(6, 6), unknown_3: payload.substr(14, 2), rain: payload.substr(16, 2) === 'C8', temp: parseInt(payload.substr(18, 2), 16) / 2 - 35, unknown_4: payload.substr(20) }; break; case '7050': obj.type = 'beckonRequest'; break; case '7070': obj.type = 'controlRequest'; obj.messagePayload = { unknown: payload.substr(0, 2), position: parseInt(payload.substr(2, 2), 16) / 2, angle: parseInt(payload.substr(4, 2), 16) - 127, valance_1: payload.substr(6, 2), valance_2: payload.substr(8, 2) }; break; case '7071': obj.type = 'controlResponse'; obj.messagePayload = payload; break; case '8010': obj.type = 'parameterGetRequest'; obj.messagePayload = { parameter: payload.substr(0) //01000005: position, 26000046: clock timer settings, 0C000006: auto modes & limits }; break; case '8011': obj.type = 'parameterGetResponse'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '01000003': //position case '01000005': //position obj.messagePayload.type = 'position'; obj.messagePayload.position = parseInt(payload.substr(8, 2), 16) / 2; obj.messagePayload.angle = parseInt(payload.substr(10, 2), 16) - 127; obj.messagePayload.valance_1 = payload.substr(12, 2); obj.messagePayload.valance_2 = payload.substr(14, 2); break; case '0C000006': //auto modes & limits obj.messagePayload.type = 'autoSettings'; obj.messagePayload.wind = parseInt(payload.substr(8, 2), 16); obj.messagePayload.rain = parseInt(payload.substr(10, 2), 16); obj.messagePayload.sun = parseInt(payload.substr(12, 2), 16); obj.messagePayload.dusk = parseInt(payload.substr(14, 2), 16); obj.messagePayload.op = parseInt(payload.substr(16, 2), 16); break; case '26000046': obj.messagePayload.type = 'clock'; obj.messagePayload.unknown = payload.substr(8); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; case '8020': obj.type = 'parameterSetRequest'; obj.messagePayload = { parameter: payload.substr(0, 8) }; switch (obj.messagePayload.parameter) { case '0B080009': obj.messagePayload.type = 'clock'; obj.messagePayload.year = parseInt(payload.substr(8, 2), 16); obj.messagePayload.month = parseInt(payload.substr(10, 2), 16); obj.messagePayload.day = parseInt(payload.substr(12, 2), 16); obj.messagePayload.hour = parseInt(payload.substr(14, 2), 16); obj.messagePayload.minute = parseInt(payload.substr(16, 2), 16); obj.messagePayload.second = parseInt(payload.substr(18, 2), 16); obj.messagePayload.day_of_week = parseInt(payload.substr(20, 2), 16); obj.messagePayload.unknown = payload.substr(22); break; default: obj.messagePayload.type = 'unknown'; obj.messagePayload.unknown = payload.substr(8); } break; default: obj.type = 'unknown'; obj.messagePayload = payload; } return obj; } //create wms strings function encodeWMS(type, parameter) { if (!parameter) parameter = {}; switch (type) { case 'setKey': if (!parameter.key) return false; return '{K401' + parameter.key + '}'; break; case 'setScanMode': if (isNaN(parameter.channel) || !parameter.panId) return false; return '{M#' + parameter.channel + parameter.panId.match(/../g).reverse().join("") + '}'; break; case 'switchChannel': if (isNaN(parameter.channel) || !parameter.panId) return false; return '{M%' + parameter.channel + parameter.panId + '}'; break; case 'ack': if (!parameter.dst) return false; return '{R21' + parameter.dst + '50AC}'; break; case 'switchChannelRequest': //channel 17 fixed if (!parameter.panId) return false; return '{R04FFFFFF5060' + parameter.panId + '021100}'; // dst or FFFFFF??? break; case 'scanRequest': return '{R04FFFFFF7020' + parameter.panId + '02}'; break; case 'scanResponse': if (!parameter.panId || !parameter.dst) return false; return '{R01' + parameter.dst + '7021' + parameter.panId + '02}'; //fixed to deviceType 02 for now break; case 'beckonRequest': if (!parameter.dst) return false; return '{R06' + parameter.dst + '7050}'; break; case 'controlRequest': if (!parameter.dst || isNaN(parameter.position) || isNaN(parameter.angle)) return false; return '{R06' + parameter.dst + '7070' + '03' + ('0' + (Math.min(Math.max(parameter.position, 0), 100) * 2).toString(16)).substr(-2).toUpperCase() + ('0' + (Math.min(Math.max(parameter.angle, 0), 90) + 127).toString(16)).substr(-2).toUpperCase() + 'FFFF}'; //no idea how valance works break; case 'parameterGetRequest': if (!parameter.dst || !parameter.parameter) return false; return '{R06' + parameter.dst + '8010' + parameter.parameter + '}'; break; case 'parameterGetRequestPosition': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '01000005}'; break; case 'parameterGetRequestClock': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '26000046}'; break; case 'parameterGetRequestAutoSettings': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8010' + '0C000006}'; break; case 'parameterSetRequestAutoSettings': if (!parameter.dst || !parameter.parameter || isNaN(parameter.wind) || isNaN(parameter.rain) || isNaN(parameter.sun) || isNaN(parameter.dusk)) return false; return '{R06' + parameter.dst + '8020' + '0D000004' + ('0' + Math.min(Math.max(parameter.wind, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.rain, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.sun, 0), 9).toString(16)).substr(-2).toUpperCase() + ('0' + Math.min(Math.max(parameter.dusk, 0), 9).toString(16)).substr(-2).toUpperCase() + (parameter.op ? '01' : '00') + '}'; break; case 'parameterSetRequestAutoAll': if (!parameter.dst) return false; return '{R06' + parameter.dst + '8020' + '0D040001' + (parameter.op ? '01' : '00') + '}'; break; default: //unkown message type return false; break; } } //process packets function processWMS(obj) { //log(JSON.stringify(obj)); if (obj.type !== 'message') return; switch (obj.payload.type) { case 'switchChannelRequest': log('received switchChannelRequest, switching channel to ' + obj.payload.messagePayload.channel, 'debug'); writeAndWaitFor(encodeWMS('switchChannel', { channel: obj.payload.messagePayload.channel, panId: PANID }), 'a'); break; case 'scanRequest': // send scanResponse log('received scanRequest, sending scanResponse', 'debug'); writeAndWaitFor(encodeWMS('scanResponse', {dst: obj.payload.src, panId: PANID}), 'a'); break; case 'joinNetworkRequest': log('received joinNetworkRequest:', 'debug'); log('KEY: ' + obj.payload.messagePayload.networkKey); log('CHANNEL: ' + obj.payload.messagePayload.channel); log('PANID: ' + obj.payload.messagePayload.panId); writeAndWaitFor(encodeWMS('ack', {dst: obj.payload.src}), 'a'); break; case 'scanResponse': log('received scanResponse', 'debug'); log('TYPE: ' + obj.payload.messagePayload.deviceType, 'debug'); log('SNR:' + obj.payload.src, 'debug'); if (obj.payload.messagePayload.deviceType === '20') { createState(this.namespace + '.Raffstore.' + obj.payload.src + '.position', 0, false, { type: 'number', min: 0, max: 100, unit: '%' }); createState(this.namespace + '.Raffstore.' + obj.payload.src + '.angle', 0, false, { type: 'number', min: 0, max: 90, unit: '°' }, function () { if (knownDevices[obj.payload.src]) return; knownDevices[obj.payload.src] = true; var src = obj.payload.src; log('device type 20 found: ' + src); var deviceId = 'javascript.' + instance + '.' + this.namespace + '.Raffstore.' + src; on({id: deviceId + '.position', change: 'ne', ack: false}, function (obj) { //send parameter writeAndWaitFor( encodeWMS('controlRequest', { dst: src, position: obj.state.val, angle: getState(deviceId + '.angle').val }), 'r' + src + '7071' ).then(() => { var lastValue = getState(deviceId + '.position').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); var timer = setInterval(function () { //get parameter periodicaly until no change is detected log(getState(deviceId + '.position').val + ':' + lastValue, 'debug') if (getState(deviceId + '.position').val === lastValue) clearInterval(timer); lastValue = getState(deviceId + '.position').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, 3500); //setState(deviceId + '.position', getState(deviceId + '.position').val, true); }); }); on({id: deviceId + '.angle', change: 'ne', ack: false}, function (obj) { //send parameter writeAndWaitFor(encodeWMS('controlRequest', { dst: src, position: getState(deviceId + '.position').val, angle: obj.state.val }), 'r' + src + '7071' ).then(() => { var lastValue = getState(deviceId + '.angle').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); var timer = setInterval(function () { //get parameter periodicaly until no change is detected log(getState(deviceId + '.angle').val + ':' + lastValue, 'debug') if (getState(deviceId + '.angle').val === lastValue) clearInterval(timer); lastValue = getState(deviceId + '.angle').val; writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, 3500); //setState(deviceId + '.angle', getState(deviceId + '.angle').val, true); }); }); setTimeout(function () { //get parameter once writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, 5000 + Math.random() * 5000); setInterval(function () { //get parameter periodicaly writeAndWaitFor(encodeWMS('parameterGetRequestPosition', {dst: src}), 'a'); }, positionInterval * 1000 + Math.random() * 5000); }); } break; case 'parameterGetResponse': log('received parameterGetResponse', 'debug'); switch (obj.payload.messagePayload.type) { case 'position': setStateDelayed(this.namespace + '.Raffstore.' + obj.payload.src + '.position', obj.payload.messagePayload.position, true, 100, true); setStateDelayed(this.namespace + '.Raffstore.' + obj.payload.src + '.angle', obj.payload.messagePayload.angle, true, 100, true); default: break; } break; case 'weatherBroadcast': log('received weatherBroadcast', 'debug'); createState(this.namespace + '.Wetter.' + obj.payload.src + '.temp', 0, false, { type: 'number', unit: '°C', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.temp', obj.payload.messagePayload.temp, true, 100, true); }); createState(this.namespace + '.Wetter.' + obj.payload.src + '.wind', 0, false, { type: 'number', min: 0, unit: 'm/s', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.wind', obj.payload.messagePayload.wind, true, 100, true); }); createState(this.namespace + '.Wetter.' + obj.payload.src + '.lux', 0, false, { type: 'number', min: 0, unit: 'lux', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.lux', obj.payload.messagePayload.lumen, true, 100, true); }); createState(this.namespace + '.Wetter.' + obj.payload.src + '.rain', false, false, { type: 'boolean', write: false }, function () { setStateDelayed(this.namespace + '.Wetter.' + obj.payload.src + '.rain', obj.payload.messagePayload.rain, true, 100, true); }); break; default: break; } } //scan for devices function wmsScan() { writeAndWaitFor(encodeWMS('scanRequest', {panId: PANID}), 'a'); }