Skip to content

Commit

Permalink
support gateway version 1.2.42
Browse files Browse the repository at this point in the history
  • Loading branch information
AlCalzone committed Oct 31, 2017
1 parent 31c4314 commit 82ea239
Show file tree
Hide file tree
Showing 8 changed files with 220 additions and 36 deletions.
3 changes: 3 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
1 change: 1 addition & 0 deletions build/ipso/endpoints.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ export declare const endpoints: {
smartTasks: string;
gateway: string;
gatewayDetails: string;
authentication: string;
};
export declare const gatewayEndpoints: {
alexaCertificate: string;
Expand Down
1 change: 1 addition & 0 deletions build/ipso/endpoints.js
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ exports.endpoints = {
smartTasks: "15010",
gateway: "15011",
gatewayDetails: "15012",
authentication: "15011/9063",
};
exports.gatewayEndpoints = {
alexaCertificate: "9094",
Expand Down
109 changes: 92 additions & 17 deletions build/main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
Expand Down Expand Up @@ -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 */
Expand Down Expand Up @@ -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}`);
Expand Down
19 changes: 18 additions & 1 deletion io-package.json
Original file line number Diff line number Diff line change
@@ -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."
Expand Down Expand Up @@ -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": {}
}
]
}
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "iobroker.tradfri",
"version": "0.5.4",
"version": "0.5.5",
"description": "ioBroker tradfri Adapter",
"author": {
"name": "AlCalzone",
Expand Down
1 change: 1 addition & 0 deletions src/ipso/endpoints.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ export const endpoints = {
smartTasks: "15010",
gateway: "15011",
gatewayDetails: "15012",
authentication: "15011/9063",
};

export const gatewayEndpoints = {
Expand Down
120 changes: 103 additions & 17 deletions src/main.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down Expand Up @@ -362,6 +386,68 @@ let adapter: ExtendedAdapter = utils.adapter({
},
}) as ExtendedAdapter;

async function connect(hostname: string, identity: string, code: string): Promise<boolean> {
// 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

Expand Down Expand Up @@ -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
Expand Down

0 comments on commit 82ea239

Please sign in to comment.