From 82ea239e90a48547b5a2fe75744326490922fdbc Mon Sep 17 00:00:00 2001 From: Dominic Griesel Date: Tue, 31 Oct 2017 23:53:38 +0100 Subject: [PATCH] support gateway version 1.2.42 --- README.md | 3 + build/ipso/endpoints.d.ts | 1 + build/ipso/endpoints.js | 1 + build/main.js | 109 ++++++++++++++++++++++++++++------ io-package.json | 19 +++++- package.json | 2 +- src/ipso/endpoints.ts | 1 + src/main.ts | 120 ++++++++++++++++++++++++++++++++------ 8 files changed, 220 insertions(+), 36 deletions(-) diff --git a/README.md b/README.md index 6a802ac5..f0bfc550 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,9 @@ The result object `ret` looks as follows: ## Changelog +#### 0.5.5 (2017-10-31) +* (AlCalzone) Restored compatibility to Gateway version 1.2.42 + #### 0.5.4 (2017-10-29) * (AlCalzone) Brightness is now expressed in 0..100% * (AlCalzone) Fixed parsing RGB colors diff --git a/build/ipso/endpoints.d.ts b/build/ipso/endpoints.d.ts index 3e0cb822..adcd52ee 100644 --- a/build/ipso/endpoints.d.ts +++ b/build/ipso/endpoints.d.ts @@ -7,6 +7,7 @@ export declare const endpoints: { smartTasks: string; gateway: string; gatewayDetails: string; + authentication: string; }; export declare const gatewayEndpoints: { alexaCertificate: string; diff --git a/build/ipso/endpoints.js b/build/ipso/endpoints.js index 70a0f7ce..cc236864 100644 --- a/build/ipso/endpoints.js +++ b/build/ipso/endpoints.js @@ -10,6 +10,7 @@ exports.endpoints = { smartTasks: "15010", gateway: "15011", gatewayDetails: "15012", + authentication: "15011/9063", }; exports.gatewayEndpoints = { alexaCertificate: "9094", diff --git a/build/main.js b/build/main.js index cd914f8b..f9a87abd 100644 --- a/build/main.js +++ b/build/main.js @@ -66,26 +66,47 @@ let adapter = utils_1.default.adapter({ adapter.subscribeObjects(`${adapter.namespace}.*`); // add special watch for lightbulb states, so we can later sync the group states custom_subscriptions_1.subscribeStates(/L\-\d+\.lightbulb\./, groups_1.syncGroupsWithState); - // initialize CoAP client const hostname = adapter.config.host.toLowerCase(); - node_coap_client_1.CoapClient.setSecurityParams(hostname, { - psk: { "Client_identity": adapter.config.securityCode }, - }); gateway_1.gateway.requestBase = `coaps://${hostname}:5684/`; - // Try a few times to setup a working connection - const maxTries = 3; - for (let i = 1; i <= maxTries; i++) { - if (yield node_coap_client_1.CoapClient.tryToConnect(gateway_1.gateway.requestBase)) { - break; // it worked + // TODO: make this more elegant when I have the time + // we're reconnecting a bit too much + // first, check try to connect with the security code + global_1.Global.log("trying to connect with the security code", "debug"); + if (!(yield connect(hostname, "Client_identity", adapter.config.securityCode))) { + // that didn't work, so the code is wrong + return; + } + // now, if we have a stored identity, try to connect with that one + const identity = yield adapter.$getState("info.identity"); + const identityObj = yield adapter.$getObject("info.identity"); + let needsAuthentication; + if (identity == null || !("psk" in identityObj.native) || typeof identityObj.native.psk !== "string" || identityObj.native.psk.length === 0) { + global_1.Global.log("no identity stored, creating a new one", "debug"); + needsAuthentication = true; + } + else if (!(yield connect(hostname, identity.val, identityObj.native.psk))) { + global_1.Global.log("stored identity has expired, creating a new one", "debug"); + // either there was no stored identity, or the current one is expired, + // so we need to get a new one + // delete the old one first + yield adapter.$setState("info.identity", "", true); + if ("psk" in identityObj.native) { + delete identityObj.native.psk; + yield adapter.$setObject("info.identity", identityObj); } - else if (i < maxTries) { - global_1.Global.log(`Could not connect to gateway, try #${i}`, "warn"); - yield promises_1.wait(1000); + needsAuthentication = true; + // therefore, reconnect with the working security code + yield connect(hostname, "Client_identity", adapter.config.securityCode); + } + if (needsAuthentication) { + const authResult = yield authenticate(); + if (authResult == null) { + global_1.Global.log("authentication failed", "error"); + return; } - else if (i === maxTries) { - // no working connection - global_1.Global.log(`Could not connect to the gateway ${gateway_1.gateway.requestBase} after ${maxTries} tries!`, "error"); - global_1.Global.log(`Please check your network and adapter settings and restart the adapter!`, "error"); + global_1.Global.log(`reconnecting with the new identity`, "debug"); + if (!(yield connect(hostname, authResult.identity, authResult.psk))) { + global_1.Global.log("connection with fresh identity failed", "error"); return; } } @@ -354,6 +375,60 @@ let adapter = utils_1.default.adapter({ } }, }); +function connect(hostname, identity, code) { + return __awaiter(this, void 0, void 0, function* () { + // initialize CoAP client + node_coap_client_1.CoapClient.reset(); + node_coap_client_1.CoapClient.setSecurityParams(hostname, { + psk: { [identity]: code }, + }); + global_1.Global.log(`Connecting to gateway ${hostname}, identity = ${identity}, psk = ${code}`, "debug"); + // Try a few times to setup a working connection + const maxTries = 3; + for (let i = 1; i <= maxTries; i++) { + if (yield node_coap_client_1.CoapClient.tryToConnect(gateway_1.gateway.requestBase)) { + break; // it worked + } + else if (i < maxTries) { + global_1.Global.log(`Could not connect to gateway, try #${i}`, "warn"); + yield promises_1.wait(1000); + } + else if (i === maxTries) { + // no working connection + global_1.Global.log(`Could not connect to the gateway ${gateway_1.gateway.requestBase} after ${maxTries} tries!`, "error"); + global_1.Global.log(`Please check your network and adapter settings and restart the adapter!`, "error"); + return false; + } + } + return true; + }); +} +function authenticate() { + return __awaiter(this, void 0, void 0, function* () { + // generate a new identity + const identity = `tradfri_${Date.now()}`; + global_1.Global.log(`authenticating with identity "${identity}"`, "debug"); + // request creation of new PSK + let payload = JSON.stringify({ 9090: identity }); + payload = Buffer.from(payload); + const response = yield node_coap_client_1.CoapClient.request(`${gateway_1.gateway.requestBase}${endpoints_1.endpoints.authentication}`, "post", payload); + // check the response + if (response.code.toString() !== "2.01") { + global_1.Global.log(`unexpected response (${response.code.toString()}) to getPSK().`, "error"); + return null; + } + // the response is a buffer containing a JSON object as a string + const pskResponse = JSON.parse(response.payload.toString("utf8")); + const psk = pskResponse["9091"]; + // remember the identity/psk + yield adapter.$setState("info.identity", identity, true); + const identityObj = yield adapter.$getObject("info.identity"); + identityObj.native.psk = psk; + yield adapter.$setObject("info.identity", identityObj); + // and return it + return { identity, psk }; + }); +} // ================================== // manage devices /** Normalizes the path to a resource, so it can be used for storing the observer */ @@ -438,7 +513,7 @@ function coapCb_getAllDevices(response) { const deviceName = iobroker_objects_1.calcObjName(gateway_1.gateway.devices[id]); yield adapter.$deleteDevice(deviceName); // remove device from dictionary - delete gateway_1.gateway.groups[id]; + delete gateway_1.gateway.devices[id]; } // remove observer stopObservingResource(`${endpoints_1.endpoints.devices}/${id}`); diff --git a/io-package.json b/io-package.json index cf5e5916..4224502d 100644 --- a/io-package.json +++ b/io-package.json @@ -1,8 +1,12 @@ { "common": { "name": "tradfri", - "version": "0.5.4", + "version": "0.5.5", "news": { + "0.5.5": { + "en": "Restored compatibility to Gateway version 1.2.42", + "de": "Kompatibility zu Gateway-Version 1.2.42 wiederhergestellt" + }, "0.5.4": { "en": "Support of virtual groups. Added icons for devices.", "de": "Unterstützung virtueller Gruppen. Icons für Geräte hinzugefügt." @@ -84,6 +88,19 @@ "def": false }, "native": {} + }, + { + "_id": "info.identity", + "type": "state", + "common": { + "role": "state", + "name": "The identity to use for gateway connections", + "type": "string", + "read": true, + "write": false, + "def": "" + }, + "native": {} } ] } \ No newline at end of file diff --git a/package.json b/package.json index f2d5c07d..64e3beab 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "iobroker.tradfri", - "version": "0.5.4", + "version": "0.5.5", "description": "ioBroker tradfri Adapter", "author": { "name": "AlCalzone", diff --git a/src/ipso/endpoints.ts b/src/ipso/endpoints.ts index 548dabbb..df82fbbb 100644 --- a/src/ipso/endpoints.ts +++ b/src/ipso/endpoints.ts @@ -8,6 +8,7 @@ export const endpoints = { smartTasks: "15010", gateway: "15011", gatewayDetails: "15012", + authentication: "15011/9063", }; export const gatewayEndpoints = { diff --git a/src/main.ts b/src/main.ts index 40f4627c..3db8d5fc 100644 --- a/src/main.ts +++ b/src/main.ts @@ -70,28 +70,52 @@ let adapter: ExtendedAdapter = utils.adapter({ // add special watch for lightbulb states, so we can later sync the group states subscribeStates(/L\-\d+\.lightbulb\./, syncGroupsWithState); - // initialize CoAP client const hostname = (adapter.config.host as string).toLowerCase(); - coap.setSecurityParams(hostname, { - psk: { "Client_identity": adapter.config.securityCode }, - }); gw.requestBase = `coaps://${hostname}:5684/`; - // Try a few times to setup a working connection - const maxTries = 3; - for (let i = 1; i <= maxTries; i++) { - if (await coap.tryToConnect(gw.requestBase)) { - break; // it worked - } else if (i < maxTries) { - _.log(`Could not connect to gateway, try #${i}`, "warn"); - await wait(1000); - } else if (i === maxTries) { - // no working connection - _.log(`Could not connect to the gateway ${gw.requestBase} after ${maxTries} tries!`, "error"); - _.log(`Please check your network and adapter settings and restart the adapter!`, "error"); + // TODO: make this more elegant when I have the time + // we're reconnecting a bit too much + + // first, check try to connect with the security code + _.log("trying to connect with the security code", "debug"); + if (!await connect(hostname, "Client_identity", adapter.config.securityCode)) { + // that didn't work, so the code is wrong + return; + } + // now, if we have a stored identity, try to connect with that one + const identity = await adapter.$getState("info.identity"); + const identityObj = await adapter.$getObject("info.identity"); + let needsAuthentication: boolean; + if (identity == null || !("psk" in identityObj.native) || typeof identityObj.native.psk !== "string" || identityObj.native.psk.length === 0) { + _.log("no identity stored, creating a new one", "debug"); + needsAuthentication = true; + } else if (!await connect(hostname, identity.val, identityObj.native.psk)) { + _.log("stored identity has expired, creating a new one", "debug"); + // either there was no stored identity, or the current one is expired, + // so we need to get a new one + // delete the old one first + await adapter.$setState("info.identity", "", true); + if ("psk" in identityObj.native) { + delete identityObj.native.psk; + await adapter.$setObject("info.identity", identityObj); + } + needsAuthentication = true; + // therefore, reconnect with the working security code + await connect(hostname, "Client_identity", adapter.config.securityCode); + } + if (needsAuthentication) { + const authResult = await authenticate(); + if (authResult == null) { + _.log("authentication failed", "error"); + return; + } + _.log(`reconnecting with the new identity`, "debug"); + if (!await connect(hostname, authResult.identity, authResult.psk)) { + _.log("connection with fresh identity failed", "error"); return; } } + await adapter.$setState("info.connection", true, true); connectionAlive = true; pingTimer = setInterval(pingThread, 10000); @@ -362,6 +386,68 @@ let adapter: ExtendedAdapter = utils.adapter({ }, }) as ExtendedAdapter; +async function connect(hostname: string, identity: string, code: string): Promise { + // initialize CoAP client + coap.reset(); + coap.setSecurityParams(hostname, { + psk: { [identity]: code }, + }); + + _.log(`Connecting to gateway ${hostname}, identity = ${identity}, psk = ${code}`, "debug"); + + // Try a few times to setup a working connection + const maxTries = 3; + for (let i = 1; i <= maxTries; i++) { + if (await coap.tryToConnect(gw.requestBase)) { + break; // it worked + } else if (i < maxTries) { + _.log(`Could not connect to gateway, try #${i}`, "warn"); + await wait(1000); + } else if (i === maxTries) { + // no working connection + _.log(`Could not connect to the gateway ${gw.requestBase} after ${maxTries} tries!`, "error"); + _.log(`Please check your network and adapter settings and restart the adapter!`, "error"); + return false; + } + } + + return true; +} + +async function authenticate(): Promise<{identity: string, psk: string}> { + // generate a new identity + const identity = `tradfri_${Date.now()}`; + + _.log(`authenticating with identity "${identity}"`, "debug"); + + // request creation of new PSK + let payload: string | Buffer = JSON.stringify({ 9090: identity }); + payload = Buffer.from(payload); + const response = await coap.request( + `${gw.requestBase}${coapEndpoints.authentication}`, + "post", + payload, + ); + + // check the response + if (response.code.toString() !== "2.01") { + _.log(`unexpected response (${response.code.toString()}) to getPSK().`, "error"); + return null; + } + // the response is a buffer containing a JSON object as a string + const pskResponse = JSON.parse(response.payload.toString("utf8")); + const psk = pskResponse["9091"]; + + // remember the identity/psk + await adapter.$setState("info.identity", identity, true); + const identityObj = await adapter.$getObject("info.identity"); + identityObj.native.psk = psk; + await adapter.$setObject("info.identity", identityObj); + + // and return it + return {identity, psk}; +} + // ================================== // manage devices @@ -462,7 +548,7 @@ async function coapCb_getAllDevices(response: CoapResponse) { const deviceName = calcObjName(gw.devices[id]); await adapter.$deleteDevice(deviceName); // remove device from dictionary - delete gw.groups[id]; + delete gw.devices[id]; } // remove observer